wsy182 2024-12-01 15:46:50 +08:00
parent 64c7b47a4b
commit efc71af70c
4 changed files with 167 additions and 53 deletions

View File

@ -37,6 +37,7 @@ def draw_tile(engine):
# 返回摸到的牌和游戏是否结束的标志 # 返回摸到的牌和游戏是否结束的标志
return tile, False # 返回摸到的牌对象和游戏继续的标志 return tile, False # 返回摸到的牌对象和游戏继续的标志
def discard_tile(self, tile): def discard_tile(self, tile):
""" """
当前玩家打牌逻辑记录打出的牌和当前牌河信息 当前玩家打牌逻辑记录打出的牌和当前牌河信息
@ -96,6 +97,7 @@ def peng(self, tile):
logger.info(f"玩家 {player} 碰了一张牌: {tile}。当前明牌: {self.state.melds[player]}") logger.info(f"玩家 {player} 碰了一张牌: {tile}。当前明牌: {self.state.melds[player]}")
def gang(self, tile, mode): def gang(self, tile, mode):
""" """
当前玩家杠牌逻辑记录杠牌类型和状态更新 当前玩家杠牌逻辑记录杠牌类型和状态更新
@ -137,7 +139,6 @@ def gang(self, tile, mode):
raise ValueError("无效的杠牌类型,仅支持 'ming''an''bu'") raise ValueError("无效的杠牌类型,仅支持 'ming''an''bu'")
def check_blood_battle(self): def check_blood_battle(self):
""" """
检查游戏是否流局或血战结束记录状态 检查游戏是否流局或血战结束记录状态
@ -189,8 +190,9 @@ def set_missing_suit(player, game_state):
def check_other_players(self, tile): def check_other_players(self, tile):
""" """
检查其他玩家是否可以对打出的牌进行操作如碰胡牌 检查其他玩家是否可以对打出的牌进行操作如胡牌
如果有玩家选择碰或杠修改游戏状态和出牌顺序 优先级为胡牌 > 杠牌 > 碰牌
如果有玩家选择操作修改游戏状态和出牌顺序
""" """
current_player = self.state.current_player current_player = self.state.current_player
actions_taken = False actions_taken = False
@ -199,30 +201,32 @@ def check_other_players(self, tile):
if player == current_player: if player == current_player:
continue continue
# 检查是否可以碰 # 优先检查胡牌
if self.state.hands[player].tile_count[tile] >= 2:
logger.info(f"玩家 {player} 可以碰玩家 {current_player} 的牌: {tile}")
if self.handle_peng(player, tile): # 执行碰牌逻辑
actions_taken = True
break # 碰牌后不检查其他玩家
# 检查是否可以杠
if self.state.hands[player].tile_count[tile] >= 3:
logger.info(f"玩家 {player} 可以杠玩家 {current_player} 的牌: {tile}")
if self.handle_gang(player, tile, mode="ming"): # 执行明杠逻辑
actions_taken = True
break # 杠牌后不检查其他玩家
# 检查是否可以胡牌
if self.can_win(self.state.hands[player], self.state.melds[player], self.state.missing_suits[player]): if self.can_win(self.state.hands[player], self.state.melds[player], self.state.missing_suits[player]):
logger.info(f"玩家 {player} 可以胡玩家 {current_player} 的牌: {tile}") logger.info(f"玩家 {player} 可以胡玩家 {current_player} 的牌: {tile}")
self.handle_win(player, current_player, tile) self.handle_win(player, current_player, tile)
actions_taken = True actions_taken = True
break # 胡牌后结束 break # 胡牌后结束
# 检查是否可以杠牌
if self.state.hands[player].tile_count[tile] >= 3:
logger.info(f"玩家 {player} 可以杠玩家 {current_player} 的牌: {tile}")
if self.handle_gang(player, tile, mode="ming"): # 执行明杠逻辑
actions_taken = True
break # 杠牌后不检查其他玩家
# 检查是否可以碰牌
if self.state.hands[player].tile_count[tile] >= 2:
logger.info(f"玩家 {player} 可以碰玩家 {current_player} 的牌: {tile}")
if self.handle_peng(player, tile): # 执行碰牌逻辑
actions_taken = True
break # 碰牌后不检查其他玩家
if not actions_taken: if not actions_taken:
logger.info(f"玩家 {current_player} 打出的牌 {tile} 没有触发其他玩家的操作") logger.info(f"玩家 {current_player} 打出的牌 {tile} 没有触发其他玩家的操作")
def handle_peng(self, player, tile): def handle_peng(self, player, tile):
""" """
处理玩家碰牌逻辑并更新出牌顺序 处理玩家碰牌逻辑并更新出牌顺序
@ -252,6 +256,7 @@ def handle_peng(self, player, tile):
self.discard_tile(player, chosen_tile) self.discard_tile(player, chosen_tile)
return True return True
def get_player_discard_choice(self, player): def get_player_discard_choice(self, player):
""" """
模拟获取玩家打牌的选择 模拟获取玩家打牌的选择
@ -265,6 +270,7 @@ def get_player_discard_choice(self, player):
logger.info(f"玩家 {player} 选择打出牌: {chosen_tile}") logger.info(f"玩家 {player} 选择打出牌: {chosen_tile}")
return chosen_tile return chosen_tile
def handle_gang(self, player, tile, mode): def handle_gang(self, player, tile, mode):
""" """
处理玩家杠牌逻辑并更新状态 处理玩家杠牌逻辑并更新状态
@ -290,6 +296,7 @@ def handle_gang(self, player, tile, mode):
self.draw_tile_for_player(player) self.draw_tile_for_player(player)
return True return True
def random_discard_tile(engine): def random_discard_tile(engine):
""" """
当前玩家随机选择一张牌打出优先打缺门牌 当前玩家随机选择一张牌打出优先打缺门牌
@ -344,3 +351,53 @@ def random_choice(hand, missing_suit):
# 如果缺门牌不存在,从剩余牌中随机选择 # 如果缺门牌不存在,从剩余牌中随机选择
index = random.randint(0, len(hand) - 1) index = random.randint(0, len(hand) - 1)
return hand[index] return hand[index]
def should_gang(ai_player, state, gang_type):
"""
判断 AI 是否选择杠牌
:param ai_player: 当前玩家索引
:param state: 游戏状态对象
:param gang_type: 杠牌类型暗杠或明杠
:return: True 表示杠False 表示不杠
"""
# 获取当前玩家分数
current_score = state.scores[ai_player]
# 获取当前玩家手牌
hand = state.hands[ai_player]
# 获取局势信息
draw_counts = state.draw_counts
remaining_tiles = state.remaining_tiles
# 基础策略:如果当前分数远低于平均分,优先杠
average_score = sum(state.scores) / len(state.scores)
if current_score < average_score * 0.8:
return True
# 检查听牌状态:如果杠后听牌,优先杠
# if is_ready_to_win(hand, state.melds[ai_player], state.missing_suits[ai_player]):
# return True
# 根据杠牌类型调整策略
if gang_type == "暗杠":
# 暗杠通常安全,偏向杠
return True
elif gang_type == "明杠":
# 明杠可能被针对,后期优先考虑杠
return remaining_tiles < 30 # 剩余牌少于 30 张时偏向杠
# 默认不杠
return False
def select_discard_tile(self, player):
"""
选择要打出的牌此处为简单示例可接入 AI 策略
"""
hand = self.state.hands[player]
# 优先打出缺门牌
for tile in hand.tiles:
if tile.suit == self.state.missing_suits[player]:
return tile
# 如果没有缺门牌,随机打出一张
return hand.tiles[0]

View File

@ -2,7 +2,7 @@ import random
from loguru import logger from loguru import logger
from src.engine.actions import set_missing_suit from src.engine.actions import set_missing_suit
from src.engine.chengdu_mahjong_state import ChengduMahjongState from src.engine.chengdu_mahjong_state import ChengduMahjongState
from src.engine.actions import draw_tile, discard_tile, peng, gang, check_blood_battle from src.engine.actions import draw_tile, discard_tile, peng, gang, check_blood_battle,should_gang,random_choice
class ChengduMahjongEngine: class ChengduMahjongEngine:
@ -19,7 +19,7 @@ class ChengduMahjongEngine:
logger.info("游戏初始化...") logger.info("游戏初始化...")
# 确定庄家(掷骰子) # 确定庄家(掷骰子)
self.state.current_player = random.randint(0, 3) self.state.current_player = random.randint(0, 3)
logger.info(f"庄家确定为玩家 {self.state.current_player}") logger.info(f"庄家确定为玩家: {self.state.current_player}")
logger.info("游戏初始化完成,准备开始!") logger.info("游戏初始化完成,准备开始!")
@ -40,38 +40,55 @@ class ChengduMahjongEngine:
logger.info("发牌结束并完成缺门设置!") logger.info("发牌结束并完成缺门设置!")
def play_turn(self): def play_turn(self):
""" if self.state.draw_counts[self.current_player] == 0:
进行一回合的操作当前玩家摸牌出牌 # 判断庄家是否天胡
""" tianhu = self.state.can_win(
logger.info(f"轮到玩家 {self.state.current_player} 行动") self.state.hands[self.current_player],
self.state.melds[self.current_player],
self.state.missing_suits[self.current_player],
)
if tianhu:
# 天胡结算
self.state.winners.append(self.current_player)
self.state.print_game_state(self.current_player)
for i in range(4):
if i != self.current_player:
self.state.scores[i] -= self.state.bottom_score * 2 ** 5
self.state.scores[self.current_player] += self.state.bottom_score * 2 ** 5
else:
# 判断是否可以杠
if self.state.hands[self.current_player].can_gang():
# 获取杠牌类型
gang_type = self.state.hands[self.current_player].get_gang_type()
# AI 决策是否杠牌
if should_gang(self.current_player, self.state, gang_type):
self.state.hands[self.current_player].perform_gang() # 执行杠操作
self.state.print_game_state(self.current_player)
# 计算杠牌得分
if gang_type == "暗杠":
# 暗杠分数结算
for i in range(4):
if i != self.current_player:
self.state.scores[i] -= self.state.bottom_score * 2 ** 2 # 扣分
self.state.scores[self.current_player] += self.state.bottom_score * 2 ** 2 * 3 # 加分
elif gang_type == "明杠":
# 明杠分数结算
for i in range(4):
if i != self.current_player:
self.state.scores[i] -= self.state.bottom_score * 2 ** 1 # 扣分
self.state.scores[self.current_player] += self.state.bottom_score * 2 ** 1 * 3 # 加分
logger.info(f"玩家 {self.current_player} 杠牌,类型: {gang_type}")
else:
random_choice(self.state.hands[self.current_player], self.state.missing_suits[self.current_player])
# 玩家摸牌
reward, done = draw_tile(self)
if done:
logger.info("游戏因牌堆摸空而结束!")
return
# 玩家打牌(逻辑可扩展为选择最佳牌)
tile_to_discard = self.select_discard_tile(self.state.current_player)
discard_tile(self, tile_to_discard)
# 检查游戏是否结束
self.check_game_over()
# 切换到下一位玩家
self.state.current_player = (self.state.current_player + 1) % 4
def select_discard_tile(self, player):
"""
选择要打出的牌此处为简单示例可接入 AI 策略
"""
hand = self.state.hands[player]
# 优先打出缺门牌
for tile in hand.tiles:
if tile.suit == self.state.missing_suits[player]:
return tile
# 如果没有缺门牌,随机打出一张
return hand.tiles[0]
def check_game_over(self): def check_game_over(self):
""" """

View File

@ -17,12 +17,16 @@ class ChengduMahjongState:
self.deck = [MahjongTile(suit, value) for suit in ["", "", ""] for value in range(1, 10)] * 4 # 108张牌 self.deck = [MahjongTile(suit, value) for suit in ["", "", ""] for value in range(1, 10)] * 4 # 108张牌
# 当前玩家索引 # 当前玩家索引
self.current_player = 0 self.current_player = 0
# 底分
self.bottom_score = 1
# 玩家分数 # 玩家分数
self.scores = [100, 100, 100, 100] self.scores = [100, 100, 100, 100]
# 剩余牌数量 # 剩余牌数量
self.remaining_tiles = len(self.deck) self.remaining_tiles = len(self.deck)
# 胜利玩家列表 # 胜利玩家列表
self.winners = [] self.winners = []
# 记录每个玩家的抓牌次数
self.draw_counts = [0] * 4
# 缺门信息 # 缺门信息
self.missing_suits = [None] * 4 # 每个玩家的缺门("条"、"筒" 或 "万" self.missing_suits = [None] * 4 # 每个玩家的缺门("条"、"筒" 或 "万"

View File

@ -37,11 +37,47 @@ class Hand:
raise ValueError("必须是 MahjongTile 类型的牌") raise ValueError("必须是 MahjongTile 类型的牌")
return self.tile_count[tile] == 2 # 摸一张牌后总数为 3 张,才可以碰 return self.tile_count[tile] == 2 # 摸一张牌后总数为 3 张,才可以碰
def can_gang(self, tile): def can_gang(self, tile=None):
""" 判断是否可以杠即是否已经有3张相同的牌摸一张牌后可以杠 """ """
判断是否可以杠牌
两种情况
1. 当前手牌中已经有 4 张相同的牌可以选择杠
2. 当前手牌中有 3 张相同的牌摸到第 4 张后可以杠
:param tile: 需要判断的牌可以为空
:return: True 如果可以杠否则 False
"""
if tile is not None:
# 情况 1: 摸到一张牌后形成四张
if not isinstance(tile, MahjongTile): if not isinstance(tile, MahjongTile):
raise ValueError("必须是 MahjongTile 类型的牌") raise ValueError("必须是 MahjongTile 类型的牌")
return self.tile_count[tile] == 4 # 摸一张牌后总数为 4 张,才可以杠 return self.tile_count[tile] == 4
else:
# 情况 2: 手牌中已有四张一样的牌
for t, count in self.tile_count.items():
if count == 4:
return True
return False
def get_gang_type(self, tile=None):
"""
判断杠牌的类型暗杠或明杠
:param tile: 如果指定了牌检查是否可以明杠
:return: 杠牌类型"暗杠" "明杠"如果不能杠返回 None
"""
if tile is not None:
# 明杠的判断逻辑手牌中已有3张相同的牌摸到第4张
if not isinstance(tile, MahjongTile):
raise ValueError("必须是 MahjongTile 类型的牌")
if self.tile_count[tile] == 4:
return "明杠"
else:
# 暗杠的判断逻辑手牌中已有4张相同的牌
for t, count in self.tile_count.items():
if count == 4:
return "暗杠"
return None
def __repr__(self): def __repr__(self):
""" 返回手牌的字符串表示 """ """ 返回手牌的字符串表示 """