1
pull/1/head
wsy182 2024-11-30 14:08:42 +08:00
parent b5bad05205
commit 0db865854e
5 changed files with 132 additions and 22 deletions

4
configs/log_config.py Normal file
View File

@ -0,0 +1,4 @@
from loguru import logger
# 配置日志
logger.add("mahjong_ai_{time}.log", rotation="10 MB", level="DEBUG", format="{time} {level} {message}")

View File

@ -1,3 +1,7 @@
from loguru import logger
from utils import get_tile_name # 确保 get_tile_name 已在 utils.py 中定义并导入
def draw_tile(state):
"""
当前玩家摸牌的动作逻辑
@ -12,10 +16,17 @@ def draw_tile(state):
- ValueError: 如果牌堆已经空了流局条件
"""
if state.remaining_tiles == 0:
logger.warning("牌堆已空,无法摸牌!")
raise ValueError("牌堆已空") # 牌堆为空时不能摸牌
tile = state.deck.pop(0) # 从牌堆顶取出一张牌
state.remaining_tiles -= 1 # 更新牌堆剩余数量
state.hands[state.current_player][tile] += 1 # 将摸到的牌添加到当前玩家的手牌中
tile_name = get_tile_name(tile) # 获取牌的具体名称
logger.info(
f"玩家 {state.current_player} 摸到一张牌: {tile_name}(索引 {tile})。剩余牌堆数量: {state.remaining_tiles}"
)
return tile
@ -35,6 +46,13 @@ def discard_tile(state, tile):
- ValueError: 如果当前玩家的手牌中没有这张牌
"""
if state.hands[state.current_player][tile] == 0:
logger.error(f"玩家 {state.current_player} 尝试打出不存在的牌: 索引 {tile}")
raise ValueError("玩家没有这张牌") # 防止打出不存在的牌
state.hands[state.current_player][tile] -= 1 # 从手牌中移除该牌
state.discards[state.current_player].append(tile) # 将牌添加到牌河
tile_name = get_tile_name(tile) # 获取牌的具体名称
logger.info(
f"玩家 {state.current_player} 打出一张牌: {tile_name}(索引 {tile})。当前牌河: {[get_tile_name(t) for t in state.discards[state.current_player]]}"
)

View File

@ -1,4 +1,6 @@
from .game_state import ChengduMahjongState
from .utils import get_tile_name # 确保 utils 中有 get_tile_name 定义
from loguru import logger
class ChengduMahjongEngine:
@ -7,47 +9,78 @@ class ChengduMahjongEngine:
self.game_over = False
def draw_tile(self):
# 当前玩家摸牌
"""
当前玩家摸牌逻辑记录牌的详细信息和游戏状态
"""
if self.state.remaining_tiles == 0:
logger.warning("牌堆已空,游戏结束!")
self.game_over = True
return "牌堆已空"
tile = self.state.deck.pop(0)
self.state.remaining_tiles -= 1
self.state.hands[self.state.current_player][tile] += 1
tile = self.state.deck.pop(0) # 从牌堆中取出一张牌
self.state.remaining_tiles -= 1 # 更新剩余牌数
self.state.hands[self.state.current_player][tile] += 1 # 加入当前玩家手牌
tile_name = get_tile_name(tile) # 获取具体的牌名
logger.info(
f"玩家 {self.state.current_player} 摸到一张牌: {tile_name}(索引 {tile})。剩余牌堆数量: {self.state.remaining_tiles}"
)
return tile
def discard_tile(self, tile):
# 当前玩家打牌
"""
当前玩家打牌逻辑记录打出的牌和当前牌河信息
"""
if self.state.hands[self.state.current_player][tile] == 0:
raise ValueError("当前玩家没有这张牌")
self.state.hands[self.state.current_player][tile] -= 1
self.state.discards[self.state.current_player].append(tile)
logger.error(f"玩家 {self.state.current_player} 尝试打出不存在的牌: 索引 {tile}")
raise ValueError("玩家没有这张牌")
self.state.hands[self.state.current_player][tile] -= 1 # 从手牌中移除
self.state.discards[self.state.current_player].append(tile) # 加入牌河
tile_name = get_tile_name(tile) # 获取具体的牌名
logger.info(
f"玩家 {self.state.current_player} 打出一张牌: {tile_name}(索引 {tile})。当前牌河: {[get_tile_name(t) for t in self.state.discards[self.state.current_player]]}"
)
def peng(self, tile):
# 碰牌逻辑
"""
当前玩家碰牌逻辑记录碰牌操作和手牌状态
"""
player = self.state.current_player
if self.state.hands[player][tile] < 2:
logger.error(f"玩家 {player} 尝试碰牌失败: {get_tile_name(tile)}(索引 {tile}")
raise ValueError("碰牌条件不满足")
self.state.hands[player][tile] -= 2
self.state.melds[player].append(("peng", tile))
self.state.hands[player][tile] -= 2 # 减去两张牌
self.state.melds[player].append(("peng", tile)) # 加入明牌列表
tile_name = get_tile_name(tile)
logger.info(f"玩家 {player} 碰了一张牌: {tile_name}(索引 {tile})。当前明牌: {self.state.melds[player]}")
def gang(self, tile, mode="ming"):
# 杠牌逻辑
"""
当前玩家杠牌逻辑记录杠牌类型和状态更新
"""
player = self.state.current_player
tile_name = get_tile_name(tile)
if mode == "ming" and self.state.hands[player][tile] == 3:
self.state.hands[player][tile] -= 3
self.state.melds[player].append(("ming_gang", tile))
logger.info(f"玩家 {player} 明杠: {tile_name}(索引 {tile}")
elif mode == "an" and self.state.hands[player][tile] == 4:
self.state.hands[player][tile] -= 4
self.state.melds[player].append(("an_gang", tile))
logger.info(f"玩家 {player} 暗杠: {tile_name}(索引 {tile}")
else:
logger.error(f"玩家 {player} 尝试杠牌失败: {tile_name}(索引 {tile}),条件不满足")
raise ValueError("杠牌条件不满足")
def check_blood_battle(self):
"""
检查游戏是否流局或血战结束
如果满足结束条件设置 self.game_over True
检查游戏是否流局或血战结束记录状态
"""
if len(self.state.winners) >= 3 or self.state.remaining_tiles == 0:
print(f"游戏结束,赢家列表: {self.state.winners}")
logger.info(f"游戏结束,赢家列表: {self.state.winners}")
self.game_over = True

View File

@ -1,4 +1,6 @@
from utils import get_suit
from utils import get_suit, get_tile_name # 确保 get_tile_name 方法已经定义
from loguru import logger
class ChengduMahjongState:
def __init__(self):
@ -16,21 +18,44 @@ class ChengduMahjongState:
self.remaining_tiles = 108
# 胜利玩家列表
self.winners = []
# 缺门信息
self.missing_suits = [None] * 4 # 每个玩家的缺门("条"、"筒" 或 "万"
def set_missing_suit(player, missing_suit):
# 确定玩家的缺门
def set_missing_suit(self, player, missing_suit):
"""
设置玩家的缺门信息
参数:
- player: 玩家索引0-3
- missing_suit: 玩家选择的缺门"""" ""
异常:
- ValueError: 如果缺门设置无效
"""
valid_suits = ["", "", ""]
if missing_suit not in valid_suits:
logger.error(f"玩家 {player} 尝试设置无效的缺门: {missing_suit}")
raise ValueError("缺门设置无效")
player.missing_suit = missing_suit
def can_win(hand):
# 判断是否满足胡牌条件(四组+一对)
self.missing_suits[player] = missing_suit
logger.info(f"玩家 {player} 设置缺门为: {missing_suit}")
def can_win(self, hand):
"""
判断玩家的手牌是否满足胡牌条件
参数:
- hand: 玩家当前的手牌长度为108的列表
返回:
- bool: 是否满足胡牌条件
"""
from collections import Counter
# 判断缺一门
suits = [get_suit(tile) for tile, count in enumerate(hand) if count > 0]
if len(set(suits)) > 2:
logger.info(f"手牌花色: {set(suits)},未满足缺一门条件")
return False # 不满足缺一门
# 判断是否满足四组+一对
@ -41,9 +66,17 @@ class ChengduMahjongState:
counter = Counter(hand)
pairs = [tile for tile, count in counter.items() if count >= 2]
for pair in pairs:
temp_hand = hand[:]
temp_hand[pair] -= 2
# 记录尝试的对子
logger.debug(f"尝试对子: {get_tile_name(pair)}(索引 {pair}")
if is_valid_group(temp_hand):
logger.info(f"满足胡牌条件的对子: {get_tile_name(pair)}(索引 {pair}")
return True
logger.info("未找到满足胡牌条件的组合")
return False

View File

@ -2,6 +2,28 @@ def get_suit(tile_index):
"""
根据牌的索引返回花色
索引 0-35索引 36-71索引 72-107
参数:
- tile_index: 牌的索引0-107
返回:
- 花色字符串: """" ""
"""
suits = ["", "", ""]
return suits[tile_index // 36]
return suits[tile_index // 36]
def get_tile_name(tile_index):
"""
根据牌的索引返回具体的牌花色和数字
参数:
- tile_index: 牌的索引0-107
返回:
- 具体牌的字符串: 例如 "1条""9筒""5万"
"""
suits = ["", "", ""]
suit = suits[tile_index // 36] # 根据索引获取花色
number = (tile_index % 36) // 4 + 1 # 计算具体数字1-9
return f"{number}{suit}"