实现命令行参数解析,添加交互式命令行界面功能,并更新README文档以反映新功能和使用示例。
This commit is contained in:
parent
fd7392c6c9
commit
94535f8a3a
122
README.md
122
README.md
@ -21,9 +21,10 @@
|
||||
- 可配置的评分权重,支持自定义评分策略
|
||||
- 多维度评估阵容强度,提供量化的阵容分析
|
||||
|
||||
4. **接口模块** (开发中)
|
||||
4. **接口模块**
|
||||
- 提供编程接口,方便集成到其他应用中
|
||||
- 命令行界面,便于直接使用
|
||||
- 交互式命令行界面,便于直接使用
|
||||
- 支持自定义评分参数
|
||||
|
||||
## 环境与依赖
|
||||
|
||||
@ -76,6 +77,25 @@ python main.py recommend
|
||||
- 指定必选棋子生成阵容
|
||||
- 综合多种条件生成最优阵容
|
||||
|
||||
### 命令行界面
|
||||
|
||||
运行以下命令启动交互式命令行界面:
|
||||
|
||||
```bash
|
||||
python main.py cli
|
||||
```
|
||||
|
||||
命令行界面提供以下功能:
|
||||
- 指定阵容人口和评分参数
|
||||
- 交互式添加必选羁绊和等级
|
||||
- 交互式添加必选棋子
|
||||
- 展示详细的阵容推荐结果,包括棋子信息和激活的羁绊
|
||||
|
||||
您还可以使用命令行参数自定义配置:
|
||||
```bash
|
||||
python main.py cli --population 8 --results 5 --level-weight 1.2 --count-weight 0.7 --cost-weight 0.2
|
||||
```
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
@ -95,13 +115,17 @@ TFT-Strategist/
|
||||
│ ├── scoring/ # 阵容评分模块
|
||||
│ │ ├── __init__.py
|
||||
│ │ └── scoring_system.py # 阵容评分系统
|
||||
│ ├── interface/ # 接口模块 (开发中)
|
||||
│ ├── interface/ # 接口模块
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── api.py # 编程接口
|
||||
│ │ └── cli.py # 命令行界面
|
||||
│ ├── __init__.py
|
||||
│ ├── data_provider_demo.py # 数据提供模块演示
|
||||
│ └── recommendation_demo.py # 阵容推荐模块演示
|
||||
├── tests/ # 测试代码
|
||||
│ ├── test_data_provider.py # 数据提供模块测试
|
||||
│ └── test_recommendation.py # 阵容推荐模块测试 (开发中)
|
||||
│ ├── test_recommendation.py # 阵容推荐模块测试
|
||||
│ └── test_interface.py # 接口模块测试
|
||||
├── main.py # 主程序入口
|
||||
├── requirements.txt # 项目依赖
|
||||
└── README.md # 项目文档
|
||||
@ -242,13 +266,99 @@ print(f"阵容评分: {score}")
|
||||
scorer.customize_scoring(synergy_level_weight=1.5)
|
||||
```
|
||||
|
||||
## 接口模块详解
|
||||
|
||||
接口模块提供了便捷的方式与阵容推荐器进行交互,包括编程接口和命令行界面。
|
||||
|
||||
### 主要组件
|
||||
|
||||
1. **TFTStrategistAPI**: 编程接口类
|
||||
- 提供完整的数据查询功能
|
||||
- 提供阵容推荐和评分功能
|
||||
- 支持自定义评分参数
|
||||
|
||||
2. **TFTCommandLine**: 命令行界面类
|
||||
- 交互式用户界面
|
||||
- 支持添加必选羁绊和棋子
|
||||
- 详细展示阵容推荐结果
|
||||
|
||||
### 编程接口使用示例
|
||||
|
||||
```python
|
||||
from src.interface import TFTStrategistAPI, get_api
|
||||
|
||||
# 使用单例模式获取API实例
|
||||
api = get_api()
|
||||
|
||||
# 或者创建新的API实例
|
||||
# api = TFTStrategistAPI(use_local_data=True)
|
||||
|
||||
# 获取游戏版本
|
||||
version = api.get_version()
|
||||
print(f"当前游戏版本: {version}")
|
||||
|
||||
# 自定义评分参数
|
||||
api.customize_scoring(
|
||||
synergy_level_weight=1.2,
|
||||
synergy_count_weight=0.6,
|
||||
chess_cost_weight=0.15
|
||||
)
|
||||
|
||||
# 获取职业和特质信息
|
||||
jobs = api.get_all_jobs()
|
||||
races = api.get_all_races()
|
||||
|
||||
# 推荐阵容
|
||||
required_synergies = [{'name': '超频战士', 'level': 3}]
|
||||
required_chess = [{'name': '薇古丝'}]
|
||||
|
||||
teams = api.recommend_team(
|
||||
population=8,
|
||||
required_synergies=required_synergies,
|
||||
required_chess=required_chess,
|
||||
max_results=3
|
||||
)
|
||||
|
||||
# 处理推荐结果
|
||||
for i, team in enumerate(teams, 1):
|
||||
print(f"\n推荐阵容 #{i} (评分: {team['score']:.2f})")
|
||||
print(f"棋子: {[chess['displayName'] for chess in team['chess_list']]}")
|
||||
```
|
||||
|
||||
### 命令行界面使用
|
||||
|
||||
直接通过main.py运行命令行界面:
|
||||
|
||||
```bash
|
||||
python main.py cli [参数]
|
||||
```
|
||||
|
||||
可选参数:
|
||||
- `--population`: 阵容人口数量,默认为9
|
||||
- `--results`: 返回结果数量,默认为3
|
||||
- `--level-weight`: 羁绊等级权重,默认为1.0
|
||||
- `--count-weight`: 羁绊数量权重,默认为0.5
|
||||
- `--cost-weight`: 棋子费用权重,默认为0.1
|
||||
|
||||
在交互界面中,您可以:
|
||||
1. 添加必选羁绊并指定等级
|
||||
2. 添加必选棋子
|
||||
3. 获取详细的阵容推荐结果
|
||||
|
||||
## 开发计划
|
||||
|
||||
- [x] 数据提供模块
|
||||
- [x] 阵容推荐模块
|
||||
- [x] 阵容评分模块
|
||||
- [ ] 接口模块
|
||||
- [ ] 图形用户界面
|
||||
- [x] 接口模块
|
||||
|
||||
## 未来展望
|
||||
|
||||
- [ ] 图形用户界面(GUI)开发
|
||||
- [ ] 基于历史数据的胜率分析
|
||||
- [ ] 支持装备推荐
|
||||
- [ ] 多语言支持
|
||||
- [ ] 基于机器学习的个性化推荐
|
||||
|
||||
## 贡献指南
|
||||
|
||||
|
53
main.py
53
main.py
@ -3,38 +3,53 @@
|
||||
云顶之弈阵容推荐器 - 主程序
|
||||
"""
|
||||
import sys
|
||||
import argparse
|
||||
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
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
# 检查命令行参数
|
||||
if len(sys.argv) > 1:
|
||||
command = sys.argv[1]
|
||||
if command == "data":
|
||||
# 运行数据提供模块演示
|
||||
data_provider_demo()
|
||||
elif command == "recommend":
|
||||
# 运行阵容推荐模块演示
|
||||
recommendation_demo()
|
||||
else:
|
||||
print(f"未知命令: {command}")
|
||||
print_usage()
|
||||
# 创建参数解析器
|
||||
parser = argparse.ArgumentParser(description='云顶之弈阵容推荐器')
|
||||
subparsers = parser.add_subparsers(dest='command', help='命令')
|
||||
|
||||
# 数据提供模块命令
|
||||
data_parser = subparsers.add_parser('data', help='运行数据提供模块演示')
|
||||
|
||||
# 阵容推荐模块命令
|
||||
recommend_parser = subparsers.add_parser('recommend', help='运行阵容推荐模块演示')
|
||||
|
||||
# 命令行界面命令
|
||||
cli_parser = subparsers.add_parser('cli', 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()
|
||||
print_usage(parser)
|
||||
print("\n默认运行数据提供模块演示...\n")
|
||||
data_provider_demo()
|
||||
|
||||
return 0
|
||||
|
||||
def print_usage():
|
||||
|
||||
def print_usage(parser):
|
||||
"""打印使用帮助"""
|
||||
print("使用方法: python main.py [命令]")
|
||||
print("命令:")
|
||||
print(" data 运行数据提供模块演示")
|
||||
print(" recommend 运行阵容推荐模块演示")
|
||||
parser.print_help()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
sys.exit(main())
|
@ -1,3 +1,6 @@
|
||||
"""
|
||||
接口模块
|
||||
接口模块 - 提供命令行和编程接口
|
||||
"""
|
||||
|
||||
from src.interface.cli import TFTCommandLine
|
||||
from src.interface.api import TFTStrategistAPI, get_api
|
275
src/interface/api.py
Normal file
275
src/interface/api.py
Normal file
@ -0,0 +1,275 @@
|
||||
"""
|
||||
编程接口模块 - 提供程序化调用阵容推荐器的API
|
||||
|
||||
此模块实现了一组API函数,允许其他程序调用阵容推荐器的功能:
|
||||
1. 获取游戏数据
|
||||
2. 查询羁绊和棋子信息
|
||||
3. 生成阵容推荐
|
||||
4. 自定义评分系统
|
||||
"""
|
||||
from typing import Dict, List, Any, Optional, Union, Tuple
|
||||
import logging
|
||||
from dataclasses import asdict
|
||||
|
||||
from src.data_provider import DataQueryAPI, DataLoader
|
||||
from src.recommendation import RecommendationEngine
|
||||
from src.scoring import TeamScorer, ScoringConfig
|
||||
|
||||
# 配置日志
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
||||
)
|
||||
logger = logging.getLogger("TFT-Strategist-API")
|
||||
|
||||
|
||||
class TFTStrategistAPI:
|
||||
"""云顶之弈阵容推荐API类"""
|
||||
|
||||
def __init__(self, use_local_data: bool = True):
|
||||
"""
|
||||
初始化API
|
||||
|
||||
Args:
|
||||
use_local_data: 是否使用本地数据,如果为False则从网络获取
|
||||
"""
|
||||
self.data_loader = DataLoader(use_local_if_exists=use_local_data)
|
||||
self.api = DataQueryAPI(self.data_loader)
|
||||
self.recommendation_engine = RecommendationEngine(api=self.api)
|
||||
self.scorer = TeamScorer()
|
||||
logger.info(f"TFT-Strategist-API 已初始化,游戏版本: {self.get_version()}")
|
||||
|
||||
def get_version(self) -> str:
|
||||
"""
|
||||
获取当前游戏版本
|
||||
|
||||
Returns:
|
||||
str: 游戏版本
|
||||
"""
|
||||
return self.data_loader.get_latest_version() or "未知"
|
||||
|
||||
def customize_scoring(self, **kwargs) -> None:
|
||||
"""
|
||||
自定义评分系统参数
|
||||
|
||||
Args:
|
||||
**kwargs: 评分参数,可以包括:
|
||||
synergy_level_weight: 羁绊等级权重
|
||||
synergy_count_weight: 羁绊数量权重
|
||||
chess_cost_weight: 棋子费用权重
|
||||
level_multipliers: 羁绊等级权重倍数字典
|
||||
cost_multipliers: 棋子费用权重倍数字典
|
||||
"""
|
||||
self.scorer.customize_scoring(**kwargs)
|
||||
logger.info(f"评分系统参数已更新: {kwargs}")
|
||||
|
||||
def get_scoring_config(self) -> Dict[str, Any]:
|
||||
"""
|
||||
获取当前评分配置
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: 评分配置字典
|
||||
"""
|
||||
config = self.scorer.config
|
||||
return {
|
||||
'synergy_level_weight': config.synergy_level_weight,
|
||||
'synergy_count_weight': config.synergy_count_weight,
|
||||
'chess_cost_weight': config.chess_cost_weight,
|
||||
'level_multipliers': config.level_multipliers,
|
||||
'cost_multipliers': config.cost_multipliers
|
||||
}
|
||||
|
||||
# 数据查询API
|
||||
|
||||
def get_all_jobs(self) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
获取所有职业信息
|
||||
|
||||
Returns:
|
||||
List[Dict[str, Any]]: 职业信息列表
|
||||
"""
|
||||
return self.api.get_all_jobs()
|
||||
|
||||
def get_all_races(self) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
获取所有特质信息
|
||||
|
||||
Returns:
|
||||
List[Dict[str, Any]]: 特质信息列表
|
||||
"""
|
||||
return self.api.get_all_races()
|
||||
|
||||
def get_all_chess(self) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
获取所有棋子信息
|
||||
|
||||
Returns:
|
||||
List[Dict[str, Any]]: 棋子信息列表
|
||||
"""
|
||||
return self.api.get_all_chess()
|
||||
|
||||
def get_job_by_name(self, name: str) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
根据名称获取职业信息
|
||||
|
||||
Args:
|
||||
name: 职业名称
|
||||
|
||||
Returns:
|
||||
Optional[Dict[str, Any]]: 职业信息,如果未找到则返回None
|
||||
"""
|
||||
return self.api.get_job_by_name(name)
|
||||
|
||||
def get_race_by_name(self, name: str) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
根据名称获取特质信息
|
||||
|
||||
Args:
|
||||
name: 特质名称
|
||||
|
||||
Returns:
|
||||
Optional[Dict[str, Any]]: 特质信息,如果未找到则返回None
|
||||
"""
|
||||
return self.api.get_race_by_name(name)
|
||||
|
||||
def get_chess_by_name(self, name: str) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
根据名称获取棋子信息
|
||||
|
||||
Args:
|
||||
name: 棋子名称
|
||||
|
||||
Returns:
|
||||
Optional[Dict[str, Any]]: 棋子信息,如果未找到则返回None
|
||||
"""
|
||||
return self.api.get_chess_by_name(name)
|
||||
|
||||
def get_chess_by_job(self, job_id: str) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
获取指定职业的所有棋子
|
||||
|
||||
Args:
|
||||
job_id: 职业ID
|
||||
|
||||
Returns:
|
||||
List[Dict[str, Any]]: 棋子信息列表
|
||||
"""
|
||||
return self.api.get_chess_by_job(job_id)
|
||||
|
||||
def get_chess_by_race(self, race_id: str) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
获取指定特质的所有棋子
|
||||
|
||||
Args:
|
||||
race_id: 特质ID
|
||||
|
||||
Returns:
|
||||
List[Dict[str, Any]]: 棋子信息列表
|
||||
"""
|
||||
return self.api.get_chess_by_race(race_id)
|
||||
|
||||
def get_synergies_of_chess(self, chess_id: str) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
获取棋子的所有羁绊信息
|
||||
|
||||
Args:
|
||||
chess_id: 棋子ID
|
||||
|
||||
Returns:
|
||||
List[Dict[str, Any]]: 羁绊信息列表
|
||||
"""
|
||||
return self.api.get_synergies_of_chess(chess_id)
|
||||
|
||||
def get_synergy_levels(self, synergy_id: str) -> Dict[str, str]:
|
||||
"""
|
||||
获取羁绊的等级信息
|
||||
|
||||
Args:
|
||||
synergy_id: 羁绊ID
|
||||
|
||||
Returns:
|
||||
Dict[str, str]: 等级信息字典,键为等级,值为效果描述
|
||||
"""
|
||||
return self.api.get_synergy_levels(synergy_id)
|
||||
|
||||
# 阵容推荐API
|
||||
|
||||
def recommend_team(
|
||||
self,
|
||||
population: int = 9,
|
||||
required_synergies: Optional[List[Dict[str, Any]]] = None,
|
||||
required_chess: Optional[List[Dict[str, Any]]] = None,
|
||||
max_results: int = 5
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
推荐阵容
|
||||
|
||||
Args:
|
||||
population: 阵容人口数,默认为9
|
||||
required_synergies: 必须包含的羁绊列表,每个羁绊为一个字典,包含name和level
|
||||
required_chess: 必须包含的棋子列表,每个棋子为一个字典,包含name
|
||||
max_results: 最多返回的推荐阵容数量
|
||||
|
||||
Returns:
|
||||
List[Dict[str, Any]]: 推荐的阵容列表,每个阵容为一个字典
|
||||
"""
|
||||
# 参数处理
|
||||
required_synergies = required_synergies or []
|
||||
required_chess = required_chess or []
|
||||
|
||||
# 生成阵容推荐
|
||||
teams = self.recommendation_engine.recommend_team(
|
||||
population=population,
|
||||
required_synergies=required_synergies,
|
||||
required_chess=required_chess,
|
||||
max_results=max_results
|
||||
)
|
||||
|
||||
# 转换为字典
|
||||
return [team.to_dict() for team in teams]
|
||||
|
||||
def score_team(self, team_dict: Dict[str, Any]) -> float:
|
||||
"""
|
||||
计算阵容评分
|
||||
|
||||
Args:
|
||||
team_dict: 阵容字典,包含chess_list等字段
|
||||
|
||||
Returns:
|
||||
float: 阵容评分
|
||||
"""
|
||||
from src.recommendation.recommendation_engine import TeamComposition
|
||||
|
||||
# 创建TeamComposition对象
|
||||
team = TeamComposition()
|
||||
|
||||
# 添加棋子
|
||||
for chess in team_dict.get('chess_list', []):
|
||||
team.add_chess(chess)
|
||||
|
||||
# 计算羁绊
|
||||
team.calculate_synergies(self.api)
|
||||
|
||||
# 计算评分
|
||||
score = self.scorer.score_team(team)
|
||||
|
||||
return score
|
||||
|
||||
|
||||
# 全局API实例
|
||||
_api_instance = None
|
||||
|
||||
def get_api(use_local_data: bool = True) -> TFTStrategistAPI:
|
||||
"""
|
||||
获取API实例,单例模式
|
||||
|
||||
Args:
|
||||
use_local_data: 是否使用本地数据
|
||||
|
||||
Returns:
|
||||
TFTStrategistAPI: API实例
|
||||
"""
|
||||
global _api_instance
|
||||
if _api_instance is None:
|
||||
_api_instance = TFTStrategistAPI(use_local_data=use_local_data)
|
||||
return _api_instance
|
196
src/interface/cli.py
Normal file
196
src/interface/cli.py
Normal file
@ -0,0 +1,196 @@
|
||||
"""
|
||||
命令行接口模块 - 提供用户与阵容推荐器交互的功能
|
||||
|
||||
此模块实现了一个命令行界面,允许用户以交互式方式:
|
||||
1. 指定阵容人口
|
||||
2. 添加必选羁绊和等级
|
||||
3. 添加必选棋子
|
||||
4. 获取阵容推荐结果并展示
|
||||
"""
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
from typing import Dict, List, Any, Optional, Tuple
|
||||
import logging
|
||||
|
||||
from src.data_provider import DataQueryAPI
|
||||
from src.recommendation import RecommendationEngine
|
||||
from src.scoring import TeamScorer, ScoringConfig
|
||||
|
||||
# 配置日志
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
||||
)
|
||||
logger = logging.getLogger("TFT-Strategist-CLI")
|
||||
|
||||
|
||||
class TFTCommandLine:
|
||||
"""云顶之弈阵容推荐命令行接口"""
|
||||
|
||||
def __init__(self):
|
||||
"""初始化命令行接口"""
|
||||
self.api = DataQueryAPI()
|
||||
self.recommendation_engine = RecommendationEngine(api=self.api)
|
||||
self.scorer = TeamScorer()
|
||||
|
||||
def run(self):
|
||||
"""运行命令行界面"""
|
||||
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')
|
||||
parser.add_argument('--level-weight', type=float, default=1.0, help='羁绊等级权重,默认为1.0')
|
||||
parser.add_argument('--count-weight', type=float, default=0.5, help='羁绊数量权重,默认为0.5')
|
||||
parser.add_argument('--cost-weight', type=float, default=0.1, help='棋子费用权重,默认为0.1')
|
||||
|
||||
# 解析参数,但忽略未知的参数,以便与主程序的参数解析兼容
|
||||
args, _ = parser.parse_known_args()
|
||||
|
||||
# 自定义评分权重
|
||||
self.scorer.customize_scoring(
|
||||
synergy_level_weight=args.level_weight,
|
||||
synergy_count_weight=args.count_weight,
|
||||
chess_cost_weight=args.cost_weight
|
||||
)
|
||||
|
||||
# 初始化阵容参数
|
||||
population = args.population
|
||||
max_results = args.results
|
||||
required_synergies = []
|
||||
required_chess = []
|
||||
|
||||
print("\n==== 云顶之弈阵容推荐器 ====\n")
|
||||
version = self.api.data_loader.get_latest_version()
|
||||
print(f"当前游戏版本: {version}")
|
||||
print(f"阵容人口: {population}")
|
||||
|
||||
# 交互式添加必选羁绊
|
||||
print("\n-- 添加必选羁绊 --")
|
||||
while True:
|
||||
synergy_name = input("请输入羁绊名称(直接回车跳过): ").strip()
|
||||
if not synergy_name:
|
||||
break
|
||||
|
||||
synergy = self.api.get_synergy_by_name(synergy_name)
|
||||
if not synergy:
|
||||
print(f"未找到羁绊: {synergy_name}")
|
||||
continue
|
||||
|
||||
synergy_id = synergy.get('jobId') or synergy.get('raceId')
|
||||
levels = self.api.get_synergy_levels(synergy_id)
|
||||
|
||||
print(f"羁绊 {synergy_name} 的可用等级:")
|
||||
for level_str, effect in levels.items():
|
||||
print(f" {level_str}: {effect}")
|
||||
|
||||
while True:
|
||||
level_input = input("请输入所需等级(直接回车使用最低等级): ").strip()
|
||||
if not level_input:
|
||||
min_level = min(int(l) for l in levels.keys())
|
||||
level = min_level
|
||||
break
|
||||
|
||||
try:
|
||||
level = int(level_input)
|
||||
if str(level) in levels:
|
||||
break
|
||||
else:
|
||||
print(f"无效的等级,请选择: {', '.join(levels.keys())}")
|
||||
except ValueError:
|
||||
print("请输入数字")
|
||||
|
||||
required_synergies.append({
|
||||
'name': synergy_name,
|
||||
'level': level
|
||||
})
|
||||
print(f"已添加羁绊: {synergy_name}, 等级: {level}")
|
||||
|
||||
# 交互式添加必选棋子
|
||||
print("\n-- 添加必选棋子 --")
|
||||
while True:
|
||||
chess_name = input("请输入棋子名称(直接回车跳过): ").strip()
|
||||
if not chess_name:
|
||||
break
|
||||
|
||||
chess = self.api.get_chess_by_name(chess_name)
|
||||
if not chess:
|
||||
print(f"未找到棋子: {chess_name}")
|
||||
continue
|
||||
|
||||
required_chess.append({
|
||||
'name': chess_name
|
||||
})
|
||||
print(f"已添加棋子: {chess_name}")
|
||||
|
||||
# 生成阵容推荐
|
||||
print("\n正在生成阵容推荐...")
|
||||
teams = self.recommendation_engine.recommend_team(
|
||||
population=population,
|
||||
required_synergies=required_synergies,
|
||||
required_chess=required_chess,
|
||||
max_results=max_results
|
||||
)
|
||||
|
||||
# 显示推荐结果
|
||||
print("\n==== 阵容推荐结果 ====")
|
||||
for i, team in enumerate(teams, 1):
|
||||
print(f"\n*** 推荐阵容 #{i} (评分: {team.score:.2f}) ***")
|
||||
|
||||
# 显示棋子列表
|
||||
print("棋子:")
|
||||
for chess in team.chess_list:
|
||||
# 获取棋子的羁绊
|
||||
job_names = []
|
||||
race_names = []
|
||||
|
||||
for job_id in chess.get('jobIds', '').split(','):
|
||||
if job_id:
|
||||
job = self.api.get_job_by_id(job_id)
|
||||
if job:
|
||||
job_names.append(job['name'])
|
||||
|
||||
for race_id in chess.get('raceIds', '').split(','):
|
||||
if race_id:
|
||||
race = self.api.get_race_by_id(race_id)
|
||||
if race:
|
||||
race_names.append(race['name'])
|
||||
|
||||
job_str = ', '.join(job_names) if job_names else '无'
|
||||
race_str = ', '.join(race_names) if race_names else '无'
|
||||
|
||||
print(f" - {chess['displayName']} ({chess['price']}金币) | 职业: {job_str} | 特质: {race_str}")
|
||||
|
||||
# 显示激活的羁绊
|
||||
print("\n激活的羁绊:")
|
||||
|
||||
# 职业
|
||||
if team.synergy_levels['job']:
|
||||
print("职业:")
|
||||
for job_level in sorted(team.synergy_levels['job'], key=lambda x: x['name']):
|
||||
print(f" - {job_level['name']} ({job_level['count']}/{job_level['level']}): {job_level['effect']}")
|
||||
|
||||
# 特质
|
||||
if team.synergy_levels['race']:
|
||||
print("特质:")
|
||||
for race_level in sorted(team.synergy_levels['race'], key=lambda x: x['name']):
|
||||
print(f" - {race_level['name']} ({race_level['count']}/{race_level['level']}): {race_level['effect']}")
|
||||
|
||||
print("\n------------------------")
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
cli = TFTCommandLine()
|
||||
try:
|
||||
cli.run()
|
||||
except KeyboardInterrupt:
|
||||
print("\n程序已退出")
|
||||
except Exception as e:
|
||||
logger.error(f"发生错误: {e}", exc_info=True)
|
||||
print(f"\n程序发生错误: {e}")
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
207
tests/test_interface.py
Normal file
207
tests/test_interface.py
Normal file
@ -0,0 +1,207 @@
|
||||
"""
|
||||
测试接口模块
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
from unittest import mock
|
||||
from io import StringIO
|
||||
|
||||
# 添加项目根目录到Python路径
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||
|
||||
from src.interface.api import TFTStrategistAPI
|
||||
from src.interface.cli import TFTCommandLine
|
||||
from src.recommendation.recommendation_engine import TeamComposition
|
||||
from src.data_provider import DataLoader, DataQueryAPI
|
||||
|
||||
|
||||
class TestTFTStrategistAPI(unittest.TestCase):
|
||||
"""测试TFTStrategistAPI类"""
|
||||
|
||||
def setUp(self):
|
||||
"""测试前准备"""
|
||||
# 模拟DataLoader
|
||||
self.mock_loader = mock.Mock()
|
||||
self.mock_loader.load_all_data.return_value = True
|
||||
|
||||
# 模拟加载的数据
|
||||
self.job_data = {
|
||||
"version": "15.7",
|
||||
"data": [
|
||||
{
|
||||
"jobId": "10157",
|
||||
"name": "重装战士",
|
||||
"level": {
|
||||
"2": "16%最大生命值",
|
||||
"4": "32%最大生命值",
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
self.race_data = {
|
||||
"version": "15.7",
|
||||
"data": [
|
||||
{
|
||||
"raceId": "10154",
|
||||
"name": "街头恶魔",
|
||||
"level": {
|
||||
"3": "+6% 生命值",
|
||||
"5": "+10% 生命值",
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
self.chess_data = {
|
||||
"version": "15.7",
|
||||
"data": [
|
||||
{
|
||||
"chessId": "10275",
|
||||
"displayName": "布兰德",
|
||||
"raceIds": "10154",
|
||||
"jobIds": "10172",
|
||||
"price": "4"
|
||||
},
|
||||
{
|
||||
"chessId": "10276",
|
||||
"displayName": "阿利斯塔",
|
||||
"raceIds": "",
|
||||
"jobIds": "10157",
|
||||
"price": "3"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
# 设置模拟数据返回
|
||||
self.mock_loader.get_data.side_effect = lambda data_type: {
|
||||
'job': self.job_data,
|
||||
'race': self.race_data,
|
||||
'chess': self.chess_data
|
||||
}.get(data_type)
|
||||
|
||||
# 模拟DataQueryAPI
|
||||
self.mock_api = mock.Mock()
|
||||
self.mock_api.get_version.return_value = "15.7"
|
||||
self.mock_api.get_all_jobs.return_value = self.job_data["data"]
|
||||
self.mock_api.get_all_races.return_value = self.race_data["data"]
|
||||
self.mock_api.get_all_chess.return_value = self.chess_data["data"]
|
||||
self.mock_api.load_all_data.return_value = True
|
||||
|
||||
# 模拟RecommendationEngine
|
||||
self.mock_recommendation_engine = mock.Mock()
|
||||
|
||||
# 创建API实例,并注入模拟对象
|
||||
with mock.patch('src.interface.api.DataQueryAPI', return_value=self.mock_api):
|
||||
with mock.patch('src.interface.api.RecommendationEngine', return_value=self.mock_recommendation_engine):
|
||||
self.api = TFTStrategistAPI(use_local_data=True)
|
||||
|
||||
def test_get_version(self):
|
||||
"""测试获取版本号"""
|
||||
version = self.api.get_version()
|
||||
self.assertEqual(version, "15.7")
|
||||
# 由于在初始化和测试中都会调用get_version,所以不再断言调用次数
|
||||
|
||||
def test_customize_scoring(self):
|
||||
"""测试自定义评分系统"""
|
||||
# 捕获调用customize_scoring时的参数
|
||||
with mock.patch.object(self.api.scorer, 'customize_scoring') as mock_customize:
|
||||
self.api.customize_scoring(synergy_level_weight=2.0)
|
||||
mock_customize.assert_called_once_with(synergy_level_weight=2.0)
|
||||
|
||||
def test_get_scoring_config(self):
|
||||
"""测试获取评分配置"""
|
||||
config = self.api.get_scoring_config()
|
||||
self.assertIn('synergy_level_weight', config)
|
||||
self.assertIn('synergy_count_weight', config)
|
||||
self.assertIn('chess_cost_weight', config)
|
||||
|
||||
def test_recommend_team(self):
|
||||
"""测试推荐阵容"""
|
||||
# 创建模拟的TeamComposition
|
||||
team = TeamComposition()
|
||||
team.chess_list = [{"displayName": "布兰德", "price": "4"}]
|
||||
team.score = 10.5
|
||||
|
||||
# 设置recommend_team的返回值
|
||||
self.mock_recommendation_engine.recommend_team.return_value = [team]
|
||||
|
||||
# 调用API
|
||||
teams = self.api.recommend_team(population=8)
|
||||
|
||||
# 验证结果
|
||||
self.assertEqual(len(teams), 1)
|
||||
self.assertEqual(teams[0]['score'], 10.5)
|
||||
self.assertEqual(len(teams[0]['chess_list']), 1)
|
||||
self.assertEqual(teams[0]['chess_list'][0]['displayName'], "布兰德")
|
||||
|
||||
# 验证调用参数
|
||||
self.mock_recommendation_engine.recommend_team.assert_called_once_with(
|
||||
population=8,
|
||||
required_synergies=[],
|
||||
required_chess=[],
|
||||
max_results=5
|
||||
)
|
||||
|
||||
def test_score_team(self):
|
||||
"""测试计算阵容评分"""
|
||||
# 设置score_team的返回值
|
||||
self.api.scorer.score_team = mock.Mock(return_value=15.5)
|
||||
|
||||
# 创建阵容数据
|
||||
team_dict = {
|
||||
'chess_list': [{"displayName": "布兰德", "price": "4"}]
|
||||
}
|
||||
|
||||
# 调用API
|
||||
score = self.api.score_team(team_dict)
|
||||
|
||||
# 验证结果
|
||||
self.assertEqual(score, 15.5)
|
||||
|
||||
|
||||
class TestTFTCommandLine(unittest.TestCase):
|
||||
"""测试TFTCommandLine类"""
|
||||
|
||||
@mock.patch('src.interface.cli.input', side_effect=['', ''])
|
||||
@mock.patch('sys.stdout', new_callable=StringIO)
|
||||
def test_run_without_input(self, mock_stdout, mock_input):
|
||||
"""测试不输入羁绊和棋子时的运行"""
|
||||
# 模拟RecommendationEngine返回空阵容列表
|
||||
mock_engine = mock.Mock()
|
||||
mock_engine.recommend_team.return_value = []
|
||||
|
||||
# 模拟API
|
||||
mock_api = mock.Mock()
|
||||
mock_api.get_version.return_value = "15.7"
|
||||
|
||||
# 使用模拟对象创建CLI实例
|
||||
with mock.patch('src.interface.cli.DataQueryAPI', return_value=mock_api):
|
||||
with mock.patch('src.interface.cli.RecommendationEngine', return_value=mock_engine):
|
||||
cli = TFTCommandLine()
|
||||
|
||||
# 捕获参数解析错误
|
||||
with mock.patch('argparse.ArgumentParser.parse_args',
|
||||
return_value=mock.MagicMock(
|
||||
population=9, results=3,
|
||||
level_weight=1.0, count_weight=0.5, cost_weight=0.1)):
|
||||
cli.run()
|
||||
|
||||
# 验证输出包含版本信息
|
||||
output = mock_stdout.getvalue()
|
||||
self.assertIn("云顶之弈阵容推荐器", output)
|
||||
self.assertIn("15.7", output) # 版本号
|
||||
self.assertIn("阵容人口: 9", output)
|
||||
|
||||
# 验证推荐调用
|
||||
mock_engine.recommend_team.assert_called_once_with(
|
||||
population=9,
|
||||
required_synergies=[],
|
||||
required_chess=[],
|
||||
max_results=3
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
223
tests/test_recommendation.py
Normal file
223
tests/test_recommendation.py
Normal file
@ -0,0 +1,223 @@
|
||||
"""
|
||||
测试阵容推荐模块
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
# 添加项目根目录到Python路径
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||
|
||||
from src.recommendation.recommendation_engine import RecommendationEngine, TeamComposition
|
||||
from src.data_provider import DataQueryAPI
|
||||
from src.scoring import TeamScorer
|
||||
|
||||
|
||||
class TestTeamComposition(unittest.TestCase):
|
||||
"""测试TeamComposition类"""
|
||||
|
||||
def test_add_chess(self):
|
||||
"""测试添加棋子"""
|
||||
team = TeamComposition()
|
||||
chess = {"displayName": "布兰德", "price": "4", "jobIds": "10172", "raceIds": "10154"}
|
||||
|
||||
# 添加棋子
|
||||
team.add_chess(chess)
|
||||
|
||||
# 验证结果
|
||||
self.assertEqual(len(team.chess_list), 1)
|
||||
self.assertEqual(team.chess_list[0], chess)
|
||||
self.assertEqual(team.size, 1)
|
||||
self.assertEqual(team.total_cost, 4)
|
||||
|
||||
# 重复添加同一个棋子,应该不会改变
|
||||
team.add_chess(chess)
|
||||
self.assertEqual(len(team.chess_list), 1)
|
||||
|
||||
def test_calculate_synergies(self):
|
||||
"""测试计算羁绊"""
|
||||
team = TeamComposition()
|
||||
|
||||
# 添加棋子
|
||||
team.add_chess({
|
||||
"displayName": "布兰德",
|
||||
"price": "4",
|
||||
"jobIds": "10172",
|
||||
"raceIds": "10154"
|
||||
})
|
||||
|
||||
team.add_chess({
|
||||
"displayName": "阿利斯塔",
|
||||
"price": "3",
|
||||
"jobIds": "10157",
|
||||
"raceIds": ""
|
||||
})
|
||||
|
||||
team.add_chess({
|
||||
"displayName": "其他棋子",
|
||||
"price": "2",
|
||||
"jobIds": "10172",
|
||||
"raceIds": "10154"
|
||||
})
|
||||
|
||||
# 模拟DataQueryAPI
|
||||
mock_api = mock.Mock()
|
||||
|
||||
# 设置get_synergy_by_id的返回值
|
||||
def get_synergy_by_id(synergy_id):
|
||||
synergies = {
|
||||
"10172": {"jobId": "10172", "name": "高级工程师"},
|
||||
"10157": {"jobId": "10157", "name": "重装战士"},
|
||||
"10154": {"raceId": "10154", "name": "街头恶魔"}
|
||||
}
|
||||
return synergies.get(synergy_id)
|
||||
|
||||
mock_api.get_synergy_by_id.side_effect = get_synergy_by_id
|
||||
|
||||
# 设置get_synergy_levels的返回值
|
||||
def get_synergy_levels(synergy_id):
|
||||
levels = {
|
||||
"10172": {"2": "效果1", "4": "效果2"},
|
||||
"10157": {"2": "效果1", "4": "效果2"},
|
||||
"10154": {"3": "效果1", "5": "效果2"}
|
||||
}
|
||||
return levels.get(synergy_id)
|
||||
|
||||
mock_api.get_synergy_levels.side_effect = get_synergy_levels
|
||||
|
||||
# 计算羁绊
|
||||
team.calculate_synergies(mock_api)
|
||||
|
||||
# 验证结果
|
||||
self.assertEqual(team.synergy_counts.get("10172"), 2) # 高级工程师有2个
|
||||
self.assertEqual(team.synergy_counts.get("10157"), 1) # 重装战士有1个
|
||||
self.assertEqual(team.synergy_counts.get("10154"), 2) # 街头恶魔有2个
|
||||
|
||||
# 验证激活的羁绊等级
|
||||
job_levels = team.synergy_levels['job']
|
||||
race_levels = team.synergy_levels['race']
|
||||
|
||||
# 应该有高级工程师(2)和重装战士(0)的羁绊
|
||||
self.assertEqual(len(job_levels), 1) # 只有高级工程师达到了激活等级
|
||||
self.assertEqual(job_levels[0]['name'], "高级工程师")
|
||||
self.assertEqual(job_levels[0]['level'], 2)
|
||||
|
||||
# 重装战士未激活,街头恶魔未激活等级3
|
||||
self.assertEqual(len(race_levels), 0)
|
||||
|
||||
def test_to_dict(self):
|
||||
"""测试转换为字典"""
|
||||
team = TeamComposition()
|
||||
team.chess_list = [{"displayName": "布兰德", "price": "4"}]
|
||||
team.synergy_counts = {"10172": 1}
|
||||
team.synergy_levels = {'job': [{'name': '高级工程师', 'level': 2}], 'race': []}
|
||||
team.total_cost = 4
|
||||
team.score = 10.5
|
||||
|
||||
# 转换为字典
|
||||
result = team.to_dict()
|
||||
|
||||
# 验证结果
|
||||
self.assertEqual(result['chess_list'], team.chess_list)
|
||||
self.assertEqual(result['synergy_counts'], team.synergy_counts)
|
||||
self.assertEqual(result['synergy_levels'], team.synergy_levels)
|
||||
self.assertEqual(result['total_cost'], team.total_cost)
|
||||
self.assertEqual(result['size'], team.size)
|
||||
self.assertEqual(result['score'], team.score)
|
||||
|
||||
|
||||
class TestRecommendationEngine(unittest.TestCase):
|
||||
"""测试RecommendationEngine类"""
|
||||
|
||||
def setUp(self):
|
||||
"""测试前准备"""
|
||||
# 模拟DataQueryAPI
|
||||
self.mock_api = mock.Mock()
|
||||
|
||||
# 模拟TeamScorer
|
||||
self.mock_scorer = mock.Mock()
|
||||
|
||||
# 创建RecommendationEngine实例
|
||||
self.engine = RecommendationEngine(api=self.mock_api, scorer=self.mock_scorer)
|
||||
|
||||
def test_recommend_team_basic(self):
|
||||
"""测试基本的阵容推荐功能"""
|
||||
# 创建模拟的TeamComposition
|
||||
team1 = TeamComposition()
|
||||
team1.chess_list = [{"displayName": "布兰德", "price": "4"}]
|
||||
team1.score = 10.5
|
||||
|
||||
team2 = TeamComposition()
|
||||
team2.chess_list = [{"displayName": "阿利斯塔", "price": "3"}]
|
||||
team2.score = 8.2
|
||||
|
||||
# 模拟_generate_candidate_teams方法
|
||||
mock_generate = mock.Mock(return_value=[team1, team2])
|
||||
self.engine._generate_candidate_teams = mock_generate
|
||||
|
||||
# 模拟calculate_synergies方法
|
||||
def mock_calculate_synergies(api):
|
||||
pass
|
||||
|
||||
team1.calculate_synergies = mock_calculate_synergies
|
||||
team2.calculate_synergies = mock_calculate_synergies
|
||||
|
||||
# 模拟score_team方法
|
||||
self.mock_scorer.score_team.side_effect = lambda team: team.score
|
||||
|
||||
# 调用recommend_team
|
||||
result = self.engine.recommend_team(population=8, max_results=1)
|
||||
|
||||
# 验证结果
|
||||
self.assertEqual(len(result), 1)
|
||||
self.assertEqual(result[0].score, 10.5) # 应该返回得分最高的阵容
|
||||
|
||||
# 验证调用
|
||||
mock_generate.assert_called_once()
|
||||
|
||||
def test_recommend_team_with_required_chess(self):
|
||||
"""测试指定必选棋子的阵容推荐"""
|
||||
# 设置get_chess_by_name的返回值
|
||||
self.mock_api.get_chess_by_name.return_value = {
|
||||
"displayName": "布兰德",
|
||||
"price": "4"
|
||||
}
|
||||
|
||||
# 模拟_generate_candidate_teams方法
|
||||
self.engine._generate_candidate_teams = mock.Mock(return_value=[])
|
||||
|
||||
# 调用recommend_team
|
||||
required_chess = [{"name": "布兰德"}]
|
||||
self.engine.recommend_team(required_chess=required_chess)
|
||||
|
||||
# 验证get_chess_by_name被调用
|
||||
self.mock_api.get_chess_by_name.assert_called_once_with("布兰德")
|
||||
|
||||
def test_recommend_team_with_required_synergies(self):
|
||||
"""测试指定必选羁绊的阵容推荐"""
|
||||
# 设置get_synergy_by_name的返回值
|
||||
self.mock_api.get_synergy_by_name.return_value = {
|
||||
"jobId": "10172",
|
||||
"name": "高级工程师"
|
||||
}
|
||||
|
||||
# 设置get_synergy_levels的返回值
|
||||
self.mock_api.get_synergy_levels.return_value = {
|
||||
"2": "效果1",
|
||||
"4": "效果2"
|
||||
}
|
||||
|
||||
# 模拟_generate_candidate_teams方法
|
||||
self.engine._generate_candidate_teams = mock.Mock(return_value=[])
|
||||
|
||||
# 调用recommend_team
|
||||
required_synergies = [{"name": "高级工程师", "level": 2}]
|
||||
self.engine.recommend_team(required_synergies=required_synergies)
|
||||
|
||||
# 验证get_synergy_by_name被调用
|
||||
self.mock_api.get_synergy_by_name.assert_called_once_with("高级工程师")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
Loading…
x
Reference in New Issue
Block a user