[Asm] 纯文本查看 复制代码 """
AI策略
多难度级别 + 防守策略 + 手牌效率分析
"""
from collections import Counter
from engine.hu_checker import HuChecker
from engine.config import AI_CONFIG
class MahjongAI:
def __init__(self, difficulty=None):
self.difficulty = difficulty or AI_CONFIG['difficulty']
self.enable_defense = AI_CONFIG['enable_defense']
def choose_discard(self, hand, river=None, dangerous_tiles=None):
"""
选择打出的牌
hand: 当前手牌
river: 全局牌河(用于防守)
dangerous_tiles: 危险牌列表
"""
if self.difficulty == 'easy':
return self._easy_strategy(hand)
elif self.difficulty == 'hard':
return self._hard_strategy(hand, river, dangerous_tiles)
else: # normal
return self._normal_strategy(hand, river, dangerous_tiles)
def _easy_strategy(self, hand):
"""简单策略:随机打出,优先打边张"""
# 简单权重:红中最后打,数字大的优先打
weights = {}
for t in hand:
score = 0
if hand.count(t) >= 2:
score += 8 # 对子留着
if t == '中':
score += 15 # 红中最后打
# 数字大的优先打(简化)
if t != '中':
score -= int(t[0])
weights[t] = score
return min(weights, key=weights.get)
def _normal_strategy(self, hand, river=None, dangerous_tiles=None):
"""中等策略:计算手牌效率,适当防守"""
best_tile = None
best_score = -99999
# 尝试打出每一张牌,计算打后的听牌距离
for tile in set(hand):
test_hand = [t for t in hand if t != tile or (t == tile and hand.index(t) != hand.index(tile))]
# 确保只移除一张
test_hand = hand.copy()
if tile in test_hand:
test_hand.remove(tile)
score = self._evaluate_hand(test_hand)
# 防守扣分:危险牌额外扣分
if self.enable_defense and dangerous_tiles and tile in dangerous_tiles:
score -= 20
# 牌河出现次数少的少打(生张扣分)
if river and river.count(tile) <= 1:
score -= 3
if score > best_score:
best_score = score
best_tile = tile
return best_tile or hand[0]
def _hard_strategy(self, hand, river=None, dangerous_tiles=None):
"""困难策略:精确计算听牌数,强力防守"""
best_tile = None
best_ting_count = -1
best_score = -99999
for tile in set(hand):
test_hand = hand.copy()
if tile in test_hand:
test_hand.remove(tile)
# 计算打这张后听牌的数量
ting_count = len(HuChecker.waiting_tiles(test_hand))
# 手牌效率分
eff_score = self._evaluate_hand(test_hand)
# 综合评分:听牌数优先,其次效率分
total_score = ting_count * 100 + eff_score
# 强力防守:危险牌打了直接大扣分
if self.enable_defense and dangerous_tiles and tile in dangerous_tiles:
total_score -= 1000
# 生张扣分更严
if river and river.count(tile) == 0:
total_score -= 10
if total_score > best_score:
best_score = total_score
best_ting_count = ting_count
best_tile = tile
return best_tile or hand[0]
def _evaluate_hand(self, hand):
"""评估手牌价值(越高越好)"""
# 已经听牌,满分
if HuChecker.is_tingpai(hand):
return 1000
score = 0
cnt = Counter(hand)
wild = hand.count('中')
# 对子数量
pairs = sum(1 for k, v in cnt.items() if v >= 2 and k != '中')
score += pairs * 15
# 刻子数量
trips = sum(1 for k, v in cnt.items() if v >= 3 and k != '中')
score += trips * 30
# 杠数量
kongs = sum(1 for k, v in cnt.items() if v >= 4 and k != '中')
score += kongs * 50
# 红中数量
score += wild * 20
# 顺子潜力检测
score += self._check_sequence_potential(hand) * 5
return score
def _check_sequence_potential(self, hand):
"""检测顺子潜力"""
potential = 0
nums = {'万': [], '条': [], '筒': []}
for t in hand:
if t == '中':
continue
for suit in ['万', '条', '筒']:
if t.endswith(suit):
num = int(t[0])
nums[suit].append(num)
break
for suit in ['万', '条', '筒']:
nlist = sorted(nums[suit])
# 检测连牌
for i in range(len(nlist) - 1):
if nlist[i+1] - nlist[i] == 1:
potential += 2 # 两张连
elif nlist[i+1] - nlist[i] == 2:
potential += 1 # 卡张
return potential
def evaluate(self, hand):
"""简单评估"""
if HuChecker.can_hu(hand):
return 999
pairs = len(set([x for x in hand if hand.count(x) >= 2]))
return pairs
def get_dangerous_tiles(self, river, player_melds=None):
"""
推断可能的危险牌
用于AI防守决策
"""
dangerous = set()
# 牌河中出现少的是危险牌
cnt = Counter(river)
for tile, count in cnt.items():
if count == 0 and tile != '中':
dangerous.add(tile)
# 根据玩家碰杠推测危险牌
if player_melds:
peng_tiles = [m['tile'] for m in player_melds if m['type'] == '碰']
for t in peng_tiles:
# 碰了某张,可能在做清一色
suit = t[-1] if t != '中' else None
if suit:
for i in range(1, 10):
dangerous.add(f'{i}{suit}')
return list(dangerous)
def decide_peng_gang(self, hand, tile, my_melds):
"""
决定是否碰/杠
返回: 'peng', 'gang', None
"""
count = hand.count(tile)
# 可以杠的情况
if count >= 3:
if self.difficulty in ('normal', 'hard'):
# 碰了之后容易听牌就杠
return 'gang'
else:
return 'gang' if count >= 3 else None
# 可以碰的情况
if count >= 2:
if self.difficulty == 'easy':
return 'peng' # 简单模式有碰就碰
elif self.difficulty == 'normal':
# 中等模式:听牌附近碰
test_hand = [t for t in hand if t != tile]
test_hand = test_hand[:-1] # 移除两张
if HuChecker.is_tingpai(test_hand):
return 'peng'
return 'peng' if len(my_melds) >= 1 else None
else:
# 困难模式:计算碰后的效率
test_hand = hand.copy()
test_hand.remove(tile)
test_hand.remove(tile)
before = self._evaluate_hand(hand)
after = self._evaluate_hand(test_hand)
return 'peng' if after >= before else None
return None
def set_difficulty(self, difficulty):
"""设置难度"""
self.difficulty = difficulty |