一、麻將牌的表示
在麻將游戲中,總共有一百四十四張牌,這些牌被分為多個類別,每個類別又包含了不同的牌型。具體來說,麻將牌主要包括序數牌、字牌和花牌三大類。序數牌中,包含有萬子、條子和筒子,每種花色的序數牌都從一到九排列,每個數字有四張相同的牌。例如,一萬、二萬一直到九萬,一共三十六張萬子牌;同樣的一條到九條,組成三十六張條子牌;還有一筒到九筒,構成另外三十六張筒子牌。
字牌則有風牌和箭牌之分。風牌包括東、南、西、北四種風向牌,各有四張,總共十六張。箭牌則包含中、發、白搭三種,同樣每種四張,共計十二張。與序數牌不同的是,字牌沒有大小順序之分,它們在游戲中的作用主要是增加游戲的復雜性和多樣的胡牌方式。比如,在一些胡牌條件中,要求玩家的手牌中包含特定的字牌組合。花牌雖然在大多數麻將規則中不是必需的,但它們的存在為游戲增添了一定的趣味性。花牌包括春、夏、秋、冬和梅、蘭、竹、菊這八種,各一張,總共有八張。這些牌通常在游戲中具有特殊的作用,比如可以替代其他牌來形成特殊牌型,或者在某些計分規則中起到加分的效果。
from enum import Enumclass 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到9for _ 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 Enumclass 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到9for _ 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 Nonereturn self.tiles.pop()def discard_tile(self, tile):"""打牌到廢牌堆"""self.discard_pile.append(tile)class Player:"""表示一個玩家"""def __init__(self, name):self.name = nameself.hand = [] # 手牌self.score = 10000 # 初始分數def draw(self, deck):"""摸牌"""tile = deck.draw_tile()if tile:self.hand.append(tile)return tiledef discard(self, tile):"""打牌"""if tile in self.hand:self.hand.remove(tile)return tilereturn Noneclass 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 = Truereturn# 打牌(這里簡化為隨機打出一張牌)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) % 4def game_over(self):"""檢查游戲是否結束"""return self.game_over_flag or len(self.deck.tiles) == 0def 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 Enumclass 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到9for _ 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 Nonereturn self.tiles.pop()def discard_tile(self, tile):"""打牌到廢牌堆"""self.discard_pile.append(tile)class Player:"""表示一個玩家"""def __init__(self, name):self.name = nameself.hand = [] # 手牌self.score = 10000 # 初始分數def draw(self, deck):"""摸牌"""tile = deck.draw_tile()if tile:self.hand.append(tile)return tiledef discard(self, tile):"""打牌"""if tile in self.hand:self.hand.remove(tile)return tilereturn Noneclass 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] += 1else:tile_count[tile_str] = 1# 檢查是否所有牌都是對子for count in tile_count.values():if count != 2:return Falsereturn Truedef 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 = Truereturn# 打牌(這里簡化為隨機打出一張牌)if self.check_exact_win(player):print(f"{player.name} 七對胡牌!")self.game_over_flag = Truereturndiscarded_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) % 4def game_over(self):"""檢查游戲是否結束"""return self.game_over_flag or len(self.deck.tiles) == 0def 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()
要實現胡牌判定功能,首先需要對玩家的手牌進行分析。手牌通常是一個包含多張牌的列表,程序需要檢查這個列表是否滿足胡牌的條件。這涉及到對牌的排序、分組和模式匹配。比如,程序需要能夠識別出牌中的順子(三種連續的序數牌)和刻子(三張相同的牌),并確保這些組合的數量和結構符合胡牌的要求。在實現過程中,遞歸和回溯算法常常被用來窮舉可能的牌型組合。這是因為麻將牌的組合方式非常多,直接遍歷所有可能性可能會導致效率低下。遞歸算法可以有效地分解問題,將手牌分成不同的部分進行檢查,并在發現不符合條件時及時回溯,嘗試其他組合方式。
此外,胡牌判定還需要考慮一些特殊情況,比如是否有剩余的牌未被使用,或者是否滿足某些特殊的加分條件。例如,“碰碰胡”要求所有的組合都是刻子,這就需要程序在判定時特別檢查是否存在順子。為了提高判定的效率和準確性,通常會將手牌進行預處理,比如排序和分類。這樣可以減少不必要的計算,加快判定過程。同時,為了處理不同的麻將規則變體,胡牌判定邏輯需要具有一定的靈活性和可配置性。可以通過參數化的方式,讓程序能夠適應不同的規則要求。