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

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. **接口模块**
- 提供编程接口,方便集成到其他应用中
@ -77,6 +78,21 @@ python main.py recommend
- 指定必选棋子生成阵容
- 综合多种条件生成最优阵容
### 评分系统演示
运行以下命令启动评分系统的演示程序:
```bash
python src/scoring_demo.py
```
演示程序将展示以下功能:
- 使用默认权重进行阵容评分
- 使用配置文件中的自定义权重进行评分
- 通过代码动态设置特定羁绊和棋子的权重
- 比较不同权重设置下的评分差异
- 动态修改配置文件并重新加载
### 命令行界面
运行以下命令启动交互式命令行界面:
@ -103,7 +119,8 @@ TFT-Strategist/
├── data/ # 数据存储目录
│ ├── chess.json # 棋子数据
│ ├── job.json # 职业数据
│ └── race.json # 特质数据
│ ├── race.json # 特质数据
│ └── weights_config.yaml # 自定义权重配置文件
├── src/ # 源代码
│ ├── data_provider/ # 数据提供模块
│ │ ├── __init__.py
@ -114,14 +131,16 @@ TFT-Strategist/
│ │ └── recommendation_engine.py # 阵容推荐引擎
│ ├── scoring/ # 阵容评分模块
│ │ ├── __init__.py
│ │ └── scoring_system.py # 阵容评分系统
│ │ ├── scoring_system.py # 阵容评分系统
│ │ └── config_loader.py # 配置加载器
│ ├── interface/ # 接口模块
│ │ ├── __init__.py
│ │ ├── api.py # 编程接口
│ │ └── cli.py # 命令行界面
│ ├── __init__.py
│ ├── data_provider_demo.py # 数据提供模块演示
│ └── recommendation_demo.py # 阵容推荐模块演示
│ ├── recommendation_demo.py # 阵容推荐模块演示
│ └── scoring_demo.py # 评分系统演示
├── tests/ # 测试代码
│ ├── test_data_provider.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
from src.scoring import TeamScorer, ScoringConfig
from src.scoring import TeamScorer
# 创建自定义评分配置
config = ScoringConfig(
synergy_level_weight=1.2, # 增加羁绊等级权重
chess_cost_weight=0.5 # 增加棋子费用权重
)
# 使用默认权重初始化评分器
scorer = TeamScorer()
# 初始化评分系统
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)
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
pandas>=1.3.0
requests>=2.25.1
pandas>=1.2.4
pyyaml>=6.0
pytest>=7.0.0
numpy>=1.20.0
matplotlib>=3.4.0
pytest>=6.2.5
numpy>=1.20.3
tqdm>=4.62.3

View File

@ -2,5 +2,6 @@
阵容评分模块
"""
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. 羁绊协同性
3. 棋子费用分布
4. 阵容整体强度
5. 自定义羁绊和棋子权重
"""
from typing import Dict, List, Any, Optional
import logging
from dataclasses import dataclass
import os
from .config_loader import ConfigLoader, create_default_config
# 配置日志
logging.basicConfig(
@ -32,6 +35,12 @@ class ScoringConfig:
# 棋子费用权重倍数
cost_multipliers: Dict[str, float] = None
# 自定义羁绊权重 (新增)
synergy_weights: Dict[str, float] = None
# 自定义棋子权重 (新增)
chess_weights: Dict[str, float] = None
def __post_init__(self):
"""初始化默认值"""
if self.level_multipliers is None:
@ -57,6 +66,13 @@ class ScoringConfig:
'4': 3.0,
'5': 4.0
}
# 初始化自定义权重为空字典
if self.synergy_weights is None:
self.synergy_weights = {}
if self.chess_weights is None:
self.chess_weights = {}
class TeamScorer:
@ -64,14 +80,56 @@ class TeamScorer:
阵容评分系统负责对阵容进行综合评分
"""
def __init__(self, config: Optional[ScoringConfig] = None):
def __init__(self, config: Optional[ScoringConfig] = None, config_path: Optional[str] = None):
"""
初始化阵容评分系统
Args:
config: 评分配置如果为None则使用默认配置
config_path: 外部配置文件路径如果提供则从中加载自定义权重
"""
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:
"""
@ -92,12 +150,16 @@ class TeamScorer:
# 3. 棋子费用评分
chess_cost_score = self._score_chess_cost(team)
# 4. 阵容整体评分
# 综合以上三项,加权求和
# 4. 自定义羁绊和棋子权重评分 (新增)
custom_weight_score = self._score_custom_weights(team)
# 5. 阵容整体评分
# 综合以上四项,加权求和
total_score = (
synergy_level_score * self.config.synergy_level_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
@ -182,6 +244,49 @@ class TeamScorer:
# 这里可以进一步完善
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:
"""
@ -195,4 +300,36 @@ class TeamScorer:
setattr(self.config, key, value)
logger.info(f"设置评分参数 {key} = {value}")
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()