使用外置配置文件,代码优化

This commit is contained in:
hxuanyu 2025-04-02 10:50:51 +08:00
parent 078fadb033
commit 1053ea7697
6 changed files with 316 additions and 81 deletions

120
README.md
View File

@ -51,6 +51,16 @@ pip install -r requirements.txt
## 快速开始
### 查看帮助信息
运行以下命令查看主程序的帮助信息:
```bash
python main.py --help
```
输出结果会显示所有可用的子命令和选项。
### 数据提供模块演示
运行以下命令启动数据提供模块的演示程序:
@ -83,7 +93,7 @@ python main.py recommend
运行以下命令启动评分系统的演示程序:
```bash
python src/scoring_demo.py
python main.py scoring
```
演示程序将展示以下功能:
@ -93,6 +103,12 @@ python src/scoring_demo.py
- 比较不同权重设置下的评分差异
- 动态修改配置文件并重新加载
您还可以指定自定义配置文件:
```bash
python main.py scoring --config path/to/custom_config.yaml
```
### 命令行界面
运行以下命令启动交互式命令行界面:
@ -112,6 +128,13 @@ python main.py cli
python main.py cli --population 8 --results 5 --level-weight 1.2 --count-weight 0.7 --cost-weight 0.2
```
参数说明:
- `--population`阵容人口数量范围1-10默认为9
- `--results`推荐结果数量默认为3
- `--level-weight`羁绊等级权重默认为1.0
- `--count-weight`羁绊数量权重默认为0.5
- `--cost-weight`棋子费用权重默认为0.1
## 项目结构
```
@ -140,7 +163,8 @@ TFT-Strategist/
│ ├── __init__.py
│ ├── data_provider_demo.py # 数据提供模块演示
│ ├── recommendation_demo.py # 阵容推荐模块演示
│ └── scoring_demo.py # 评分系统演示
│ ├── scoring_demo.py # 评分系统演示
│ └── test_scoring.py # 评分测试模块
├── tests/ # 测试代码
│ ├── test_data_provider.py # 数据提供模块测试
│ ├── test_recommendation.py # 阵容推荐模块测试
@ -250,75 +274,93 @@ for team in teams:
## 阵容评分模块详解
阵容评分模块提供了对阵容进行综合评分的功能,考虑多个因素
阵容评分模块是系统的核心评估组件,负责为推荐的阵容提供量化的评分指标
### 主要组件
1. **TeamScorer**: 阵容评分系统
1. **TeamScorer**: 阵容评分
- 综合考虑羁绊数量、等级、棋子费用等因素
- 提供可配置的评分权重
- 多维度评估阵容强度
- 支持自定义权重配置
- 提供灵活的评分API
2. **ScoringConfig**: 评分配置类
- 定义各项评分权重
- 支持自定义评分策略
- 定义评分所需的各项权重参数
- 提供默认配置值
- 支持通过外部配置文件进行定制
3. **ConfigLoader**: 配置加载器
- 从外部YAML或JSON文件加载自定义权重设置
- 支持修改和重新加载配置
- 从YAML或JSON文件加载评分配置
- 支持热重载配置
- 提供丰富的配置访问API
### 评分因素
### 配置文件详解
1. **羁绊等级评分**:基于羁绊等级和额外激活的羁绊单位数量
2. **羁绊种类数量评分**:基于激活的羁绊种类数量
3. **棋子费用评分**:基于阵容中棋子的费用分布
4. **自定义权重评分**:基于配置文件中设置的特定羁绊和棋子权重
### 自定义权重配置
你可以通过编辑 `data/weights_config.yaml` 文件来自定义评分权重:
系统支持通过YAML配置文件data/weights_config.yaml来自定义评分权重
```yaml
# 基础权重配置
base_weights:
synergy_level_weight: 1.0 # 羁绊等级权重
synergy_count_weight: 0.5 # 羁绊种类数量权重
chess_cost_weight: 0.1 # 棋子费用权重
synergy_level_weight: 1.0 # 羁绊等级权重
synergy_count_weight: 0.5 # 羁绊数量权重
chess_cost_weight: 0.1 # 棋子费用权重
# 羁绊权重配置(值越大,该羁绊在评分中的权重越高)
# 羁绊权重配置
synergy_weights:
重装战士: 1.5
重装战士: 1.5 # 特定羁绊权重
魔法师: 1.2
# 可以添加更多羁绊...
# 其他羁绊...
# 棋子权重配置(值越大,该棋子在评分中的权重越高)
# 棋子权重配置
chess_weights:
亚索: 1.5
亚索: 1.5 # 特定棋子权重
艾希: 1.2
# 可以添加更多棋子...
# 其他棋子...
# 羁绊等级权重
synergy_level_weights:
'1': 1.0 # 1级羁绊权重
'2': 1.2 # 2级羁绊权重
# 其他等级...
# 棋子费用权重
cost_weights:
'1': 1.0 # 1费棋子权重
'2': 1.2 # 2费棋子权重
# 其他费用...
```
配置文件中的权重设置将直接影响阵容评分结果。权重值越大,对应的因素在评分中的影响越大。
### 使用示例
```python
from src.scoring import TeamScorer
from src.data_provider import DataQueryAPI
from src.recommendation import RecommendationEngine
# 使用默认权重初始化评分器
# 初始化评分器(自动加载配置文件)
scorer = TeamScorer()
# 从配置文件加载自定义权重
scorer_with_config = TeamScorer(config_path="data/weights_config.yaml")
# 或者指定配置文件路径
scorer = TeamScorer(config_path="path/to/custom_config.yaml")
# 动态设置特定羁绊的权重
scorer.set_synergy_weight("狙神", 2.0)
scorer.set_synergy_weight("刺客", 1.8)
# 动态修改权重
scorer.set_synergy_weight("忍者", 2.0) # 提高"忍者"羁绊的权重
scorer.set_chess_weight("劫", 1.8) # 提高"劫"的权重
# 动态设置特定棋子的权重
scorer.set_chess_weight("艾希", 2.0)
scorer.set_chess_weight("薇恩", 1.8)
# 重新加载配置文件
scorer.reload_config()
# 评分阵容
score = scorer.score_team(team)
# 为阵容评分
api = DataQueryAPI()
engine = RecommendationEngine(api)
teams = engine.recommend_team(population=8)
for team in teams:
score = scorer.score_team(team)
print(f"阵容评分: {score}")
print(f"棋子: {[chess['displayName'] for chess in team.chess_list]}")
print(f"羁绊: {team.synergy_levels}")
```
## 接口模块详解

95
main.py
View File

@ -4,52 +4,83 @@
"""
import sys
import argparse
import logging
import os
from src.data_provider_demo import main as data_provider_demo
from src.recommendation_demo import main as recommendation_demo
from src.interface.cli import main as cli_main
from src.scoring.scoring_system import TeamScorer
from src.test_scoring import main as test_scoring
# 配置日志
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger("TFT-Strategist")
def main():
"""主函数"""
# 创建参数解析器
parser = argparse.ArgumentParser(description='云顶之弈阵容推荐器')
subparsers = parser.add_subparsers(dest='command', help='命令')
"""主程序入口"""
parser = argparse.ArgumentParser(description="云顶之弈阵容推荐器")
# 数据提供模块命令
data_parser = subparsers.add_parser('data', help='运行数据提供模块演示')
# 添加子命令
subparsers = parser.add_subparsers(dest="command", help="可用命令")
# 阵容推荐模块命令
recommend_parser = subparsers.add_parser('recommend', help='运行阵容推荐模块演示')
# 数据提供模块演示
data_parser = subparsers.add_parser("data", help="数据提供模块演示")
# 命令行界面命令
cli_parser = subparsers.add_parser('cli', help='运行命令行界面')
# 阵容推荐模块演示
recommend_parser = subparsers.add_parser("recommend", help="阵容推荐模块演示")
# 解析参数
# 命令行界面
cli_parser = subparsers.add_parser("cli", help="交互式命令行界面")
cli_parser.add_argument("--population", type=int, help="阵容人口数量")
cli_parser.add_argument("--results", type=int, help="推荐结果数量")
cli_parser.add_argument("--level-weight", type=float, help="羁绊等级权重")
cli_parser.add_argument("--count-weight", type=float, help="羁绊数量权重")
cli_parser.add_argument("--cost-weight", type=float, help="棋子费用权重")
# 评分模块测试 (新增)
scoring_parser = subparsers.add_parser("scoring", help="评分模块测试")
scoring_parser.add_argument("--config", type=str, help="指定配置文件路径")
# 解析命令行参数
args = parser.parse_args()
# 根据命令执行相应的功能
if args.command == 'data':
# 运行数据提供模块演示
data_provider_demo()
elif args.command == 'recommend':
# 运行阵容推荐模块演示
recommendation_demo()
elif args.command == 'cli':
# 运行命令行界面
return cli_main()
else:
# 默认运行数据提供模块演示
print_usage(parser)
print("\n默认运行数据提供模块演示...\n")
# 根据子命令执行相应功能
if args.command == "data":
logger.info("启动数据提供模块演示")
# 调用数据提供模块演示函数
data_provider_demo()
return 0
def print_usage(parser):
"""打印使用帮助"""
parser.print_help()
elif args.command == "recommend":
logger.info("启动阵容推荐模块演示")
# 调用阵容推荐模块演示函数
recommendation_demo()
elif args.command == "cli":
logger.info("启动交互式命令行界面")
# 将命令行参数传递给cli_main函数
return cli_main(
population=args.population,
results=args.results,
level_weight=args.level_weight,
count_weight=args.count_weight,
cost_weight=args.cost_weight
)
elif args.command == "scoring":
logger.info("启动评分模块测试")
if args.config:
# 如果指定了配置文件路径,使用该路径进行测试
test_scoring(config_path=args.config)
else:
# 否则使用默认配置文件
test_scoring()
else:
parser.print_help()
if __name__ == "__main__":
sys.exit(main())

View File

@ -34,8 +34,17 @@ class TFTCommandLine:
self.recommendation_engine = RecommendationEngine(api=self.api)
self.scorer = TeamScorer()
def run(self):
"""运行命令行界面"""
def run(self, population=None, results=None, level_weight=None, count_weight=None, cost_weight=None):
"""
运行命令行界面
Args:
population (int, optional): 阵容人口数量
results (int, optional): 推荐结果数量
level_weight (float, optional): 羁绊等级权重
count_weight (float, optional): 羁绊数量权重
cost_weight (float, optional): 棋子费用权重
"""
parser = argparse.ArgumentParser(description='云顶之弈阵容推荐器命令行界面', add_help=False)
parser.add_argument('--population', type=int, default=9, help='阵容人口数量默认为9')
parser.add_argument('--results', type=int, default=3, help='返回结果数量默认为3')
@ -46,16 +55,21 @@ class TFTCommandLine:
# 解析参数,但忽略未知的参数,以便与主程序的参数解析兼容
args, _ = parser.parse_known_args()
# 优先使用传入的参数,其次使用命令行参数
population = population or args.population
max_results = results or args.results
level_weight = level_weight or args.level_weight
count_weight = count_weight or args.count_weight
cost_weight = cost_weight or args.cost_weight
# 自定义评分权重
self.scorer.customize_scoring(
synergy_level_weight=args.level_weight,
synergy_count_weight=args.count_weight,
chess_cost_weight=args.cost_weight
synergy_level_weight=level_weight,
synergy_count_weight=count_weight,
chess_cost_weight=cost_weight
)
# 初始化阵容参数
population = args.population
max_results = args.results
required_synergies = []
required_chess = []
@ -178,11 +192,29 @@ class TFTCommandLine:
print("\n------------------------")
def main():
"""主函数"""
def main(population=None, results=None, level_weight=None, count_weight=None, cost_weight=None):
"""
主函数
Args:
population (int, optional): 阵容人口数量
results (int, optional): 推荐结果数量
level_weight (float, optional): 羁绊等级权重
count_weight (float, optional): 羁绊数量权重
cost_weight (float, optional): 棋子费用权重
Returns:
int: 退出码
"""
cli = TFTCommandLine()
try:
cli.run()
cli.run(
population=population,
results=results,
level_weight=level_weight,
count_weight=count_weight,
cost_weight=cost_weight
)
except KeyboardInterrupt:
print("\n程序已退出")
except Exception as e:

View File

@ -85,6 +85,24 @@ class ConfigLoader:
Dict[str, float]: 基础权重参数到权重值的映射
"""
return self.config_data.get('base_weights', {})
def get_synergy_level_weights(self) -> Dict[str, float]:
"""
获取羁绊等级权重配置
Returns:
Dict[str, float]: 羁绊等级到权重的映射
"""
return self.config_data.get('synergy_level_weights', {})
def get_cost_weights(self) -> Dict[str, float]:
"""
获取棋子费用权重配置
Returns:
Dict[str, float]: 棋子费用到权重的映射
"""
return self.config_data.get('cost_weights', {})
def create_default_config(config_path: str) -> None:
"""
@ -110,6 +128,25 @@ def create_default_config(config_path: str) -> None:
'艾希': 1.2,
'璐璐': 1.3,
'金克斯': 1.4
},
'synergy_level_weights': {
'1': 1.0,
'2': 1.2,
'3': 1.5,
'4': 1.8,
'5': 2.0,
'6': 2.3,
'7': 2.6,
'8': 3.0,
'9': 3.3,
'10': 3.5
},
'cost_weights': {
'1': 1.0,
'2': 1.2,
'3': 1.5,
'4': 1.8,
'5': 2.0
}
}

View File

@ -130,6 +130,20 @@ class TeamScorer:
if chess_weights:
self.config.chess_weights.update(chess_weights)
logger.info(f"从配置文件加载棋子权重: {len(chess_weights)}")
# 加载羁绊等级权重倍数 (使用新方法)
synergy_level_weights = loader.get_synergy_level_weights()
if synergy_level_weights:
# 将字符串键转换为整数键
level_multipliers = {int(k): float(v) for k, v in synergy_level_weights.items()}
self.config.level_multipliers = level_multipliers
logger.info(f"从配置文件加载羁绊等级权重倍数: {len(level_multipliers)}")
# 加载棋子费用权重倍数 (使用新方法)
cost_weights = loader.get_cost_weights()
if cost_weights:
self.config.cost_multipliers = cost_weights
logger.info(f"从配置文件加载棋子费用权重倍数: {len(cost_weights)}")
def score_team(self, team) -> float:
"""

79
src/test_scoring.py Normal file
View File

@ -0,0 +1,79 @@
"""
测试评分系统的配置加载功能
"""
import sys
import os
import logging
from dataclasses import dataclass
from typing import Dict, List
# 添加项目根目录到Python路径
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from src.scoring.scoring_system import TeamScorer, ScoringConfig
# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("TFT-Strategist-TestScoring")
@dataclass
class MockTeam:
"""模拟的阵容类,用于测试"""
chess_list: List[Dict]
synergy_levels: Dict
def main(config_path=None):
"""主函数"""
logger.info("开始测试评分系统...")
# 创建评分器实例
scorer = TeamScorer(config_path=config_path)
# 打印当前配置
logger.info("基础权重:")
logger.info(f" 羁绊等级权重: {scorer.config.synergy_level_weight}")
logger.info(f" 羁绊数量权重: {scorer.config.synergy_count_weight}")
logger.info(f" 棋子费用权重: {scorer.config.chess_cost_weight}")
logger.info("羁绊等级倍数:")
for level, multiplier in sorted(scorer.config.level_multipliers.items()):
logger.info(f" 等级 {level}: {multiplier}")
logger.info("棋子费用倍数:")
for cost, multiplier in sorted(scorer.config.cost_multipliers.items()):
logger.info(f" 费用 {cost}: {multiplier}")
logger.info("羁绊自定义权重 (前5个):")
for name, weight in list(scorer.config.synergy_weights.items())[:5]:
logger.info(f" {name}: {weight}")
logger.info("棋子自定义权重 (前5个):")
for name, weight in list(scorer.config.chess_weights.items())[:5]:
logger.info(f" {name}: {weight}")
# 测试评分函数
mock_team = MockTeam(
chess_list=[
{"displayName": "佛耶戈", "price": "4"},
{"displayName": "厄加特", "price": "5"},
{"displayName": "盖伦", "price": "3"},
{"displayName": "扎克", "price": "3"}
],
synergy_levels={
"job": [
{"name": "重装战士", "level": 2, "count": 2},
{"name": "斗士", "level": 2, "count": 2}
],
"race": [
{"name": "鳄霸", "level": 1, "count": 1}
]
}
)
score = scorer.score_team(mock_team)
logger.info(f"模拟阵容评分: {score}")
logger.info("测试完成!")
if __name__ == "__main__":
main()