parent
64c7b47a4b
commit
efc71af70c
|
|
@ -37,6 +37,7 @@ def draw_tile(engine):
|
|||
# 返回摸到的牌和游戏是否结束的标志
|
||||
return tile, False # 返回摸到的牌对象和游戏继续的标志
|
||||
|
||||
|
||||
def discard_tile(self, tile):
|
||||
"""
|
||||
当前玩家打牌逻辑,记录打出的牌和当前牌河信息。
|
||||
|
|
@ -96,6 +97,7 @@ def peng(self, tile):
|
|||
|
||||
logger.info(f"玩家 {player} 碰了一张牌: {tile}。当前明牌: {self.state.melds[player]}")
|
||||
|
||||
|
||||
def gang(self, tile, mode):
|
||||
"""
|
||||
当前玩家杠牌逻辑,记录杠牌类型和状态更新。
|
||||
|
|
@ -137,7 +139,6 @@ def gang(self, tile, mode):
|
|||
raise ValueError("无效的杠牌类型,仅支持 'ming'、'an' 或 'bu'")
|
||||
|
||||
|
||||
|
||||
def check_blood_battle(self):
|
||||
"""
|
||||
检查游戏是否流局或血战结束,记录状态。
|
||||
|
|
@ -189,8 +190,9 @@ def set_missing_suit(player, game_state):
|
|||
|
||||
def check_other_players(self, tile):
|
||||
"""
|
||||
检查其他玩家是否可以对打出的牌进行操作(如碰、杠、胡牌)。
|
||||
如果有玩家选择碰或杠,修改游戏状态和出牌顺序。
|
||||
检查其他玩家是否可以对打出的牌进行操作(如胡牌、杠、碰)。
|
||||
优先级为:胡牌 > 杠牌 > 碰牌。
|
||||
如果有玩家选择操作,修改游戏状态和出牌顺序。
|
||||
"""
|
||||
current_player = self.state.current_player
|
||||
actions_taken = False
|
||||
|
|
@ -199,30 +201,32 @@ def check_other_players(self, tile):
|
|||
if player == current_player:
|
||||
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]):
|
||||
logger.info(f"玩家 {player} 可以胡玩家 {current_player} 的牌: {tile}")
|
||||
self.handle_win(player, current_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.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:
|
||||
logger.info(f"玩家 {current_player} 打出的牌 {tile} 没有触发其他玩家的操作")
|
||||
|
||||
|
||||
|
||||
def handle_peng(self, player, tile):
|
||||
"""
|
||||
处理玩家碰牌逻辑并更新出牌顺序。
|
||||
|
|
@ -252,6 +256,7 @@ def handle_peng(self, player, tile):
|
|||
self.discard_tile(player, chosen_tile)
|
||||
return True
|
||||
|
||||
|
||||
def get_player_discard_choice(self, player):
|
||||
"""
|
||||
模拟获取玩家打牌的选择。
|
||||
|
|
@ -265,6 +270,7 @@ def get_player_discard_choice(self, player):
|
|||
logger.info(f"玩家 {player} 选择打出牌: {chosen_tile}")
|
||||
return chosen_tile
|
||||
|
||||
|
||||
def handle_gang(self, player, tile, mode):
|
||||
"""
|
||||
处理玩家杠牌逻辑并更新状态。
|
||||
|
|
@ -290,6 +296,7 @@ def handle_gang(self, player, tile, mode):
|
|||
self.draw_tile_for_player(player)
|
||||
return True
|
||||
|
||||
|
||||
def random_discard_tile(engine):
|
||||
"""
|
||||
当前玩家随机选择一张牌打出,优先打缺门牌。
|
||||
|
|
@ -344,3 +351,53 @@ def random_choice(hand, missing_suit):
|
|||
# 如果缺门牌不存在,从剩余牌中随机选择
|
||||
index = random.randint(0, len(hand) - 1)
|
||||
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]
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import random
|
|||
from loguru import logger
|
||||
from src.engine.actions import set_missing_suit
|
||||
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:
|
||||
|
|
@ -19,7 +19,7 @@ class ChengduMahjongEngine:
|
|||
logger.info("游戏初始化...")
|
||||
# 确定庄家(掷骰子)
|
||||
self.state.current_player = random.randint(0, 3)
|
||||
logger.info(f"庄家确定为玩家 {self.state.current_player}")
|
||||
logger.info(f"庄家确定为玩家: {self.state.current_player}")
|
||||
|
||||
logger.info("游戏初始化完成,准备开始!")
|
||||
|
||||
|
|
@ -40,38 +40,55 @@ class ChengduMahjongEngine:
|
|||
logger.info("发牌结束并完成缺门设置!")
|
||||
|
||||
def play_turn(self):
|
||||
"""
|
||||
进行一回合的操作:当前玩家摸牌、出牌。
|
||||
"""
|
||||
logger.info(f"轮到玩家 {self.state.current_player} 行动")
|
||||
if self.state.draw_counts[self.current_player] == 0:
|
||||
# 判断庄家是否天胡
|
||||
tianhu = self.state.can_win(
|
||||
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):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -17,12 +17,16 @@ class ChengduMahjongState:
|
|||
self.deck = [MahjongTile(suit, value) for suit in ["条", "筒", "万"] for value in range(1, 10)] * 4 # 108张牌
|
||||
# 当前玩家索引
|
||||
self.current_player = 0
|
||||
# 底分
|
||||
self.bottom_score = 1
|
||||
# 玩家分数
|
||||
self.scores = [100, 100, 100, 100]
|
||||
# 剩余牌数量
|
||||
self.remaining_tiles = len(self.deck)
|
||||
# 胜利玩家列表
|
||||
self.winners = []
|
||||
# 记录每个玩家的抓牌次数
|
||||
self.draw_counts = [0] * 4
|
||||
# 缺门信息
|
||||
self.missing_suits = [None] * 4 # 每个玩家的缺门("条"、"筒" 或 "万")
|
||||
|
||||
|
|
|
|||
|
|
@ -37,11 +37,47 @@ class Hand:
|
|||
raise ValueError("必须是 MahjongTile 类型的牌")
|
||||
return self.tile_count[tile] == 2 # 摸一张牌后总数为 3 张,才可以碰
|
||||
|
||||
def can_gang(self, tile):
|
||||
""" 判断是否可以杠(即是否已经有3张相同的牌,摸一张牌后可以杠) """
|
||||
def can_gang(self, tile=None):
|
||||
"""
|
||||
判断是否可以杠牌。
|
||||
两种情况:
|
||||
1. 当前手牌中已经有 4 张相同的牌,可以选择杠。
|
||||
2. 当前手牌中有 3 张相同的牌,摸到第 4 张后可以杠。
|
||||
|
||||
:param tile: 需要判断的牌(可以为空)。
|
||||
:return: True 如果可以杠,否则 False。
|
||||
"""
|
||||
if tile is not None:
|
||||
# 情况 1: 摸到一张牌后形成四张
|
||||
if not isinstance(tile, 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):
|
||||
""" 返回手牌的字符串表示 """
|
||||
|
|
|
|||
Loading…
Reference in New Issue