支持自定义羁绊和棋子的权重

This commit is contained in:
hxuanyu 2025-04-02 10:26:41 +08:00
parent 94535f8a3a
commit 9ea4c5d4d5
7 changed files with 513 additions and 27 deletions

View File

@ -20,6 +20,7 @@
- 综合考虑羁绊数量、等级、棋子费用等因素 - 综合考虑羁绊数量、等级、棋子费用等因素
- 可配置的评分权重,支持自定义评分策略 - 可配置的评分权重,支持自定义评分策略
- 多维度评估阵容强度,提供量化的阵容分析 - 多维度评估阵容强度,提供量化的阵容分析
- **支持通过外置配置文件为特定羁绊或棋子设置自定义权重**
4. **接口模块** 4. **接口模块**
- 提供编程接口,方便集成到其他应用中 - 提供编程接口,方便集成到其他应用中
@ -77,6 +78,21 @@ python main.py recommend
- 指定必选棋子生成阵容 - 指定必选棋子生成阵容
- 综合多种条件生成最优阵容 - 综合多种条件生成最优阵容
### 评分系统演示
运行以下命令启动评分系统的演示程序:
```bash
python src/scoring_demo.py
```
演示程序将展示以下功能:
- 使用默认权重进行阵容评分
- 使用配置文件中的自定义权重进行评分
- 通过代码动态设置特定羁绊和棋子的权重
- 比较不同权重设置下的评分差异
- 动态修改配置文件并重新加载
### 命令行界面 ### 命令行界面
运行以下命令启动交互式命令行界面: 运行以下命令启动交互式命令行界面:
@ -103,7 +119,8 @@ TFT-Strategist/
├── data/ # 数据存储目录 ├── data/ # 数据存储目录
│ ├── chess.json # 棋子数据 │ ├── chess.json # 棋子数据
│ ├── job.json # 职业数据 │ ├── job.json # 职业数据
│ └── race.json # 特质数据 │ ├── race.json # 特质数据
│ └── weights_config.yaml # 自定义权重配置文件
├── src/ # 源代码 ├── src/ # 源代码
│ ├── data_provider/ # 数据提供模块 │ ├── data_provider/ # 数据提供模块
│ │ ├── __init__.py │ │ ├── __init__.py
@ -114,14 +131,16 @@ TFT-Strategist/
│ │ └── recommendation_engine.py # 阵容推荐引擎 │ │ └── recommendation_engine.py # 阵容推荐引擎
│ ├── scoring/ # 阵容评分模块 │ ├── scoring/ # 阵容评分模块
│ │ ├── __init__.py │ │ ├── __init__.py
│ │ └── scoring_system.py # 阵容评分系统 │ │ ├── scoring_system.py # 阵容评分系统
│ │ └── config_loader.py # 配置加载器
│ ├── interface/ # 接口模块 │ ├── interface/ # 接口模块
│ │ ├── __init__.py │ │ ├── __init__.py
│ │ ├── api.py # 编程接口 │ │ ├── api.py # 编程接口
│ │ └── cli.py # 命令行界面 │ │ └── cli.py # 命令行界面
│ ├── __init__.py │ ├── __init__.py
│ ├── data_provider_demo.py # 数据提供模块演示 │ ├── data_provider_demo.py # 数据提供模块演示
│ └── recommendation_demo.py # 阵容推荐模块演示 │ ├── recommendation_demo.py # 阵容推荐模块演示
│ └── scoring_demo.py # 评分系统演示
├── tests/ # 测试代码 ├── tests/ # 测试代码
│ ├── test_data_provider.py # 数据提供模块测试 │ ├── test_data_provider.py # 数据提供模块测试
│ ├── test_recommendation.py # 阵容推荐模块测试 │ ├── test_recommendation.py # 阵容推荐模块测试
@ -244,26 +263,62 @@ for team in teams:
- 定义各项评分权重 - 定义各项评分权重
- 支持自定义评分策略 - 支持自定义评分策略
3. **ConfigLoader**: 配置加载器
- 从外部YAML或JSON文件加载自定义权重设置
- 支持修改和重新加载配置
### 评分因素
1. **羁绊等级评分**:基于羁绊等级和额外激活的羁绊单位数量
2. **羁绊种类数量评分**:基于激活的羁绊种类数量
3. **棋子费用评分**:基于阵容中棋子的费用分布
4. **自定义权重评分**:基于配置文件中设置的特定羁绊和棋子权重
### 自定义权重配置
你可以通过编辑 `data/weights_config.yaml` 文件来自定义评分权重:
```yaml
# 基础权重配置
base_weights:
synergy_level_weight: 1.0 # 羁绊等级权重
synergy_count_weight: 0.5 # 羁绊种类数量权重
chess_cost_weight: 0.1 # 棋子费用权重
# 羁绊权重配置(值越大,该羁绊在评分中的权重越高)
synergy_weights:
重装战士: 1.5
魔法师: 1.2
# 可以添加更多羁绊...
# 棋子权重配置(值越大,该棋子在评分中的权重越高)
chess_weights:
亚索: 1.5
艾希: 1.2
# 可以添加更多棋子...
```
### 使用示例 ### 使用示例
```python ```python
from src.scoring import TeamScorer, ScoringConfig from src.scoring import TeamScorer
# 创建自定义评分配置 # 使用默认权重初始化评分器
config = ScoringConfig( scorer = TeamScorer()
synergy_level_weight=1.2, # 增加羁绊等级权重
chess_cost_weight=0.5 # 增加棋子费用权重
)
# 初始化评分系统 # 从配置文件加载自定义权重
scorer = TeamScorer(config=config) scorer_with_config = TeamScorer(config_path="data/weights_config.yaml")
# 对阵容进行评分 # 动态设置特定羁绊的权重
scorer.set_synergy_weight("狙神", 2.0)
scorer.set_synergy_weight("刺客", 1.8)
# 动态设置特定棋子的权重
scorer.set_chess_weight("艾希", 2.0)
scorer.set_chess_weight("薇恩", 1.8)
# 评分阵容
score = scorer.score_team(team) score = scorer.score_team(team)
print(f"阵容评分: {score}")
# 动态调整评分参数
scorer.customize_scoring(synergy_level_weight=1.5)
``` ```
## 接口模块详解 ## 接口模块详解

37
data/weights_config.yaml Normal file
View File

@ -0,0 +1,37 @@
# 云顶之弈阵容评分权重配置文件
# 基础权重配置
base_weights:
synergy_level_weight: 1.0 # 羁绊等级权重
synergy_count_weight: 0.5 # 羁绊种类数量权重
chess_cost_weight: 0.1 # 棋子费用权重
# 羁绊权重配置(值越大,该羁绊在评分中的权重越高)
synergy_weights:
# 职业羁绊
重装战士: 1.5
魔法师: 1.2
神谕者: 1.3
斗士: 1.1
刺客: 1.4
狙神: 1.6
守护者: 1.3
# 特质羁绊
星神: 1.4
耀光使: 1.3
海克斯科技: 1.5
赏金猎人: 1.2
未来战士: 1.3
# 棋子权重配置(值越大,该棋子在评分中的权重越高)
chess_weights:
亚索: 1.5
艾希: 1.2
璐璐: 1.3
金克斯: 1.4
薇恩: 1.3
赛娜: 1.4
塔姆: 1.2
纳尔: 1.3
塞拉斯: 1.2

View File

@ -1,6 +1,6 @@
requests>=2.25.0 requests>=2.25.1
pandas>=1.3.0 pandas>=1.2.4
pyyaml>=6.0 pyyaml>=6.0
pytest>=7.0.0 pytest>=6.2.5
numpy>=1.20.0 numpy>=1.20.3
matplotlib>=3.4.0 tqdm>=4.62.3

View File

@ -2,5 +2,6 @@
阵容评分模块 阵容评分模块
""" """
from .scoring_system import TeamScorer, ScoringConfig from .scoring_system import TeamScorer, ScoringConfig
from .config_loader import ConfigLoader, create_default_config
__all__ = ['TeamScorer', 'ScoringConfig'] __all__ = ['TeamScorer', 'ScoringConfig', 'ConfigLoader', 'create_default_config']

View File

@ -0,0 +1,133 @@
"""
配置加载器 - 用于从外部配置文件加载自定义权重设置
此模块提供了从YAML或JSON配置文件中加载自定义权重设置的功能
支持为特定羁绊和棋子设置自定义权重
"""
import os
import json
import yaml
import logging
from typing import Dict, Any, Optional
# 配置日志
logger = logging.getLogger("TFT-Strategist-ConfigLoader")
class ConfigLoader:
"""
配置加载器用于从外部文件加载自定义权重设置
"""
def __init__(self, config_path: Optional[str] = None):
"""
初始化配置加载器
Args:
config_path: 配置文件路径如果为None则使用默认路径
"""
self.config_path = config_path or os.path.join("data", "weights_config.yaml")
self.config_data = {}
def load_config(self) -> Dict[str, Any]:
"""
加载配置文件
Returns:
Dict[str, Any]: 包含配置数据的字典
"""
if not os.path.exists(self.config_path):
logger.warning(f"配置文件不存在: {self.config_path}")
return {}
try:
file_ext = os.path.splitext(self.config_path)[1].lower()
if file_ext == '.json':
with open(self.config_path, 'r', encoding='utf-8') as f:
self.config_data = json.load(f)
elif file_ext in ['.yaml', '.yml']:
with open(self.config_path, 'r', encoding='utf-8') as f:
self.config_data = yaml.safe_load(f)
else:
logger.error(f"不支持的配置文件格式: {file_ext}")
return {}
logger.info(f"成功加载配置文件: {self.config_path}")
return self.config_data
except Exception as e:
logger.error(f"加载配置文件失败: {str(e)}")
return {}
def get_synergy_weights(self) -> Dict[str, float]:
"""
获取羁绊权重配置
Returns:
Dict[str, float]: 羁绊名称到权重的映射
"""
return self.config_data.get('synergy_weights', {})
def get_chess_weights(self) -> Dict[str, float]:
"""
获取棋子权重配置
Returns:
Dict[str, float]: 棋子名称到权重的映射
"""
return self.config_data.get('chess_weights', {})
def get_base_weights(self) -> Dict[str, float]:
"""
获取基础权重配置
Returns:
Dict[str, float]: 基础权重参数到权重值的映射
"""
return self.config_data.get('base_weights', {})
def create_default_config(config_path: str) -> None:
"""
创建默认配置文件
Args:
config_path: 配置文件路径
"""
default_config = {
'base_weights': {
'synergy_level_weight': 1.0,
'synergy_count_weight': 0.5,
'chess_cost_weight': 0.1
},
'synergy_weights': {
'重装战士': 1.5,
'魔法师': 1.2,
'神谕者': 1.3,
'斗士': 1.1
},
'chess_weights': {
'亚索': 1.5,
'艾希': 1.2,
'璐璐': 1.3,
'金克斯': 1.4
}
}
try:
file_ext = os.path.splitext(config_path)[1].lower()
os.makedirs(os.path.dirname(config_path), exist_ok=True)
if file_ext == '.json':
with open(config_path, 'w', encoding='utf-8') as f:
json.dump(default_config, f, ensure_ascii=False, indent=2)
elif file_ext in ['.yaml', '.yml']:
with open(config_path, 'w', encoding='utf-8') as f:
yaml.dump(default_config, f, allow_unicode=True, default_flow_style=False)
else:
logger.error(f"不支持的配置文件格式: {file_ext}")
return
logger.info(f"成功创建默认配置文件: {config_path}")
except Exception as e:
logger.error(f"创建默认配置文件失败: {str(e)}")

View File

@ -6,10 +6,13 @@
2. 羁绊协同性 2. 羁绊协同性
3. 棋子费用分布 3. 棋子费用分布
4. 阵容整体强度 4. 阵容整体强度
5. 自定义羁绊和棋子权重
""" """
from typing import Dict, List, Any, Optional from typing import Dict, List, Any, Optional
import logging import logging
from dataclasses import dataclass from dataclasses import dataclass
import os
from .config_loader import ConfigLoader, create_default_config
# 配置日志 # 配置日志
logging.basicConfig( logging.basicConfig(
@ -32,6 +35,12 @@ class ScoringConfig:
# 棋子费用权重倍数 # 棋子费用权重倍数
cost_multipliers: Dict[str, float] = None cost_multipliers: Dict[str, float] = None
# 自定义羁绊权重 (新增)
synergy_weights: Dict[str, float] = None
# 自定义棋子权重 (新增)
chess_weights: Dict[str, float] = None
def __post_init__(self): def __post_init__(self):
"""初始化默认值""" """初始化默认值"""
if self.level_multipliers is None: if self.level_multipliers is None:
@ -58,21 +67,70 @@ class ScoringConfig:
'5': 4.0 '5': 4.0
} }
# 初始化自定义权重为空字典
if self.synergy_weights is None:
self.synergy_weights = {}
if self.chess_weights is None:
self.chess_weights = {}
class TeamScorer: class TeamScorer:
""" """
阵容评分系统负责对阵容进行综合评分 阵容评分系统负责对阵容进行综合评分
""" """
def __init__(self, config: Optional[ScoringConfig] = None): def __init__(self, config: Optional[ScoringConfig] = None, config_path: Optional[str] = None):
""" """
初始化阵容评分系统 初始化阵容评分系统
Args: Args:
config: 评分配置如果为None则使用默认配置 config: 评分配置如果为None则使用默认配置
config_path: 外部配置文件路径如果提供则从中加载自定义权重
""" """
self.config = config if config else ScoringConfig() self.config = config if config else ScoringConfig()
# 如果提供了配置文件路径,则加载自定义权重
if config_path or os.path.exists(os.path.join("data", "weights_config.yaml")):
self._load_custom_weights(config_path)
def _load_custom_weights(self, config_path: Optional[str] = None) -> None:
"""
从配置文件加载自定义权重
Args:
config_path: 配置文件路径如果为None则使用默认路径
"""
loader = ConfigLoader(config_path)
config_data = loader.load_config()
if not config_data:
# 如果配置文件不存在或加载失败,创建默认配置文件
default_path = config_path or os.path.join("data", "weights_config.yaml")
create_default_config(default_path)
logger.info(f"已创建默认配置文件: {default_path}")
loader = ConfigLoader(default_path)
config_data = loader.load_config()
# 更新基础权重
base_weights = loader.get_base_weights()
for key, value in base_weights.items():
if hasattr(self.config, key):
setattr(self.config, key, value)
logger.info(f"从配置文件加载基础权重: {key} = {value}")
# 更新羁绊自定义权重
synergy_weights = loader.get_synergy_weights()
if synergy_weights:
self.config.synergy_weights.update(synergy_weights)
logger.info(f"从配置文件加载羁绊权重: {len(synergy_weights)}")
# 更新棋子自定义权重
chess_weights = loader.get_chess_weights()
if chess_weights:
self.config.chess_weights.update(chess_weights)
logger.info(f"从配置文件加载棋子权重: {len(chess_weights)}")
def score_team(self, team) -> float: def score_team(self, team) -> float:
""" """
对阵容进行综合评分 对阵容进行综合评分
@ -92,12 +150,16 @@ class TeamScorer:
# 3. 棋子费用评分 # 3. 棋子费用评分
chess_cost_score = self._score_chess_cost(team) chess_cost_score = self._score_chess_cost(team)
# 4. 阵容整体评分 # 4. 自定义羁绊和棋子权重评分 (新增)
# 综合以上三项,加权求和 custom_weight_score = self._score_custom_weights(team)
# 5. 阵容整体评分
# 综合以上四项,加权求和
total_score = ( total_score = (
synergy_level_score * self.config.synergy_level_weight + synergy_level_score * self.config.synergy_level_weight +
synergy_count_score * self.config.synergy_count_weight + synergy_count_score * self.config.synergy_count_weight +
chess_cost_score * self.config.chess_cost_weight chess_cost_score * self.config.chess_cost_weight +
custom_weight_score # 自定义权重分数已经包含各自的权重
) )
return total_score return total_score
@ -183,6 +245,49 @@ class TeamScorer:
return score return score
def _score_custom_weights(self, team) -> float:
"""
根据自定义权重评分
Args:
team: TeamComposition实例
Returns:
float: 自定义权重评分
"""
score = 0.0
# 应用自定义羁绊权重
if self.config.synergy_weights:
# 职业羁绊
for job_level in team.synergy_levels.get('job', []):
synergy_name = job_level.get('name', '')
if synergy_name in self.config.synergy_weights:
weight = self.config.synergy_weights[synergy_name]
level = job_level['level']
score += level * weight
logger.debug(f"应用羁绊权重: {synergy_name}, 等级: {level}, 权重: {weight}")
# 特质羁绊
for race_level in team.synergy_levels.get('race', []):
synergy_name = race_level.get('name', '')
if synergy_name in self.config.synergy_weights:
weight = self.config.synergy_weights[synergy_name]
level = race_level['level']
score += level * weight
logger.debug(f"应用羁绊权重: {synergy_name}, 等级: {level}, 权重: {weight}")
# 应用自定义棋子权重
if self.config.chess_weights:
for chess in team.chess_list:
chess_name = chess.get('displayName', '')
if chess_name in self.config.chess_weights:
weight = self.config.chess_weights[chess_name]
score += weight
logger.debug(f"应用棋子权重: {chess_name}, 权重: {weight}")
return score
def customize_scoring(self, **kwargs) -> None: def customize_scoring(self, **kwargs) -> None:
""" """
自定义评分参数 自定义评分参数
@ -196,3 +301,35 @@ class TeamScorer:
logger.info(f"设置评分参数 {key} = {value}") logger.info(f"设置评分参数 {key} = {value}")
else: else:
logger.warning(f"未知的评分参数: {key}") logger.warning(f"未知的评分参数: {key}")
def set_synergy_weight(self, synergy_name: str, weight: float) -> None:
"""
设置特定羁绊的自定义权重
Args:
synergy_name: 羁绊名称
weight: 权重值
"""
self.config.synergy_weights[synergy_name] = weight
logger.info(f"设置羁绊权重: {synergy_name} = {weight}")
def set_chess_weight(self, chess_name: str, weight: float) -> None:
"""
设置特定棋子的自定义权重
Args:
chess_name: 棋子名称
weight: 权重值
"""
self.config.chess_weights[chess_name] = weight
logger.info(f"设置棋子权重: {chess_name} = {weight}")
def reload_config(self, config_path: Optional[str] = None) -> None:
"""
重新加载配置文件
Args:
config_path: 配置文件路径如果为None则使用默认路径
"""
self._load_custom_weights(config_path)
logger.info("重新加载配置文件完成")

123
src/scoring_demo.py Normal file
View File

@ -0,0 +1,123 @@
"""
评分系统演示脚本 - 展示如何使用自定义权重功能
"""
import os
import sys
import logging
from typing import Dict, Any
# 添加项目根目录到Python路径
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from src.data_provider import DataQueryAPI
from src.recommendation import RecommendationEngine, TeamComposition
from src.scoring import TeamScorer, ScoringConfig, ConfigLoader
# 配置日志
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger("TFT-Strategist-Demo")
def print_team_info(team: TeamComposition) -> None:
"""打印阵容信息"""
print("\n=== 阵容信息 ===")
print(f"阵容人口: {len(team.chess_list)}")
print("\n棋子列表:")
for chess in team.chess_list:
print(f" {chess['displayName']} ({chess.get('price', '?')}金)")
print("\n激活的羁绊:")
if team.synergy_levels.get('job'):
print(" 职业羁绊:")
for job in team.synergy_levels['job']:
print(f" {job['name']}: {job['count']}/{job['level']}")
if team.synergy_levels.get('race'):
print(" 特质羁绊:")
for race in team.synergy_levels['race']:
print(f" {race['name']}: {race['count']}/{race['level']}")
def demo_scoring_with_custom_weights() -> None:
"""演示使用自定义权重进行评分"""
# 初始化API
api = DataQueryAPI()
# 创建推荐引擎
engine = RecommendationEngine(api)
# 1. 使用默认权重评分
print("\n=== 使用默认权重评分 ===")
scorer_default = TeamScorer()
team = engine.recommend_team(population=8)
score_default = scorer_default.score_team(team)
print(f"默认权重下的评分: {score_default:.2f}")
print_team_info(team)
# 2. 使用配置文件中的自定义权重评分
print("\n=== 使用配置文件中的自定义权重评分 ===")
scorer_file = TeamScorer(config_path="data/weights_config.yaml")
score_file = scorer_file.score_team(team)
print(f"配置文件权重下的评分: {score_file:.2f}")
print(f"评分差异: {score_file - score_default:.2f}")
# 3. 使用代码中设置的自定义权重评分
print("\n=== 使用代码中设置的自定义权重评分 ===")
scorer_code = TeamScorer()
# 设置特定羁绊的权重
scorer_code.set_synergy_weight("狙神", 2.0)
scorer_code.set_synergy_weight("刺客", 1.8)
# 设置特定棋子的权重
scorer_code.set_chess_weight("艾希", 2.0)
scorer_code.set_chess_weight("薇恩", 1.8)
score_code = scorer_code.score_team(team)
print(f"代码设置权重下的评分: {score_code:.2f}")
print(f"评分差异: {score_code - score_default:.2f}")
# 4. 根据特定羁绊推荐阵容
print("\n=== 根据特定羁绊推荐阵容 ===")
# 寻找一个包含狙神的阵容
team_with_sniper = engine.recommend_team(
population=8,
required_synergies={'job': [{'name': '狙神', 'level': 1}]}
)
# 使用不同的评分器评分
score_default = scorer_default.score_team(team_with_sniper)
score_file = scorer_file.score_team(team_with_sniper)
score_code = scorer_code.score_team(team_with_sniper)
print(f"默认权重下的评分: {score_default:.2f}")
print(f"配置文件权重下的评分: {score_file:.2f}")
print(f"代码设置权重下的评分: {score_code:.2f}")
print_team_info(team_with_sniper)
# 5. 动态修改配置文件并重新加载
print("\n=== 动态修改配置文件并重新加载 ===")
config_loader = ConfigLoader("data/weights_config.yaml")
config_data = config_loader.load_config()
# 修改配置
if 'synergy_weights' in config_data:
config_data['synergy_weights']['狙神'] = 2.5
# 保存修改后的配置
import yaml
with open("data/weights_config.yaml", 'w', encoding='utf-8') as f:
yaml.dump(config_data, f, allow_unicode=True, default_flow_style=False)
# 重新加载配置
scorer_file.reload_config()
score_reloaded = scorer_file.score_team(team_with_sniper)
print(f"重新加载配置后的评分: {score_reloaded:.2f}")
print(f"重新加载前的评分: {score_file:.2f}")
print(f"评分差异: {score_reloaded - score_file:.2f}")
if __name__ == "__main__":
demo_scoring_with_custom_weights()