完成阵容推荐器编写
This commit is contained in:
parent
b014895061
commit
fd7392c6c9
132
README.md
132
README.md
@ -11,13 +11,15 @@
|
||||
- 提供丰富的数据查询API,如获取羁绊下的棋子、棋子的羁绊详情等
|
||||
- 高效的数据处理与缓存机制
|
||||
|
||||
2. **阵容推荐模块** (开发中)
|
||||
2. **阵容推荐模块**
|
||||
- 根据用户指定的人口、必须羁绊、必选棋子自动生成阵容
|
||||
- 支持多种约束条件组合,如指定多个羁绊、多个棋子等
|
||||
- 智能选择最佳棋子组合,最大化羁绊效果
|
||||
|
||||
3. **阵容评分模块** (开发中)
|
||||
3. **阵容评分模块**
|
||||
- 综合考虑羁绊数量、等级、棋子费用等因素
|
||||
- 可配置的评分权重,支持自定义评分策略
|
||||
- 多维度评估阵容强度,提供量化的阵容分析
|
||||
|
||||
4. **接口模块** (开发中)
|
||||
- 提供编程接口,方便集成到其他应用中
|
||||
@ -60,6 +62,20 @@ python main.py data
|
||||
- 显示当前版本职业、特质和棋子的基本信息
|
||||
- 提供交互式查询界面,可以查询羁绊的棋子、棋子的羁绊等信息
|
||||
|
||||
### 阵容推荐模块演示
|
||||
|
||||
运行以下命令启动阵容推荐模块的演示程序:
|
||||
|
||||
```bash
|
||||
python main.py recommend
|
||||
```
|
||||
|
||||
演示程序将提供以下功能:
|
||||
- 指定人口数量生成最佳阵容
|
||||
- 指定必选羁绊生成阵容
|
||||
- 指定必选棋子生成阵容
|
||||
- 综合多种条件生成最优阵容
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
@ -73,14 +89,19 @@ TFT-Strategist/
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── data_loader.py # 数据加载器
|
||||
│ │ └── data_query_api.py # 数据查询API
|
||||
│ ├── recommendation/ # 阵容推荐模块 (开发中)
|
||||
│ ├── scoring/ # 阵容评分模块 (开发中)
|
||||
│ ├── recommendation/ # 阵容推荐模块
|
||||
│ │ ├── __init__.py
|
||||
│ │ └── recommendation_engine.py # 阵容推荐引擎
|
||||
│ ├── scoring/ # 阵容评分模块
|
||||
│ │ ├── __init__.py
|
||||
│ │ └── scoring_system.py # 阵容评分系统
|
||||
│ ├── interface/ # 接口模块 (开发中)
|
||||
│ ├── __init__.py
|
||||
│ └── data_provider_demo.py # 数据提供模块演示
|
||||
│ ├── data_provider_demo.py # 数据提供模块演示
|
||||
│ └── recommendation_demo.py # 阵容推荐模块演示
|
||||
├── tests/ # 测试代码
|
||||
│ └── test_data_provider.py # 数据提供模块测试
|
||||
├── config/ # 配置文件目录
|
||||
│ ├── test_data_provider.py # 数据提供模块测试
|
||||
│ └── test_recommendation.py # 阵容推荐模块测试 (开发中)
|
||||
├── main.py # 主程序入口
|
||||
├── requirements.txt # 项目依赖
|
||||
└── README.md # 项目文档
|
||||
@ -128,11 +149,104 @@ if brand:
|
||||
print(f"布兰德的羁绊: {[synergy['name'] for synergy in synergies]}")
|
||||
```
|
||||
|
||||
## 阵容推荐模块详解
|
||||
|
||||
阵容推荐模块是系统的核心,能够根据用户需求生成最优阵容。
|
||||
|
||||
### 主要组件
|
||||
|
||||
1. **RecommendationEngine**: 阵容推荐引擎
|
||||
- 支持多种约束条件组合(人口、羁绊、棋子)
|
||||
- 智能选择最佳棋子组合
|
||||
- 内置多种推荐策略
|
||||
|
||||
2. **TeamComposition**: 阵容组合类
|
||||
- 表示一个完整的云顶之弈阵容
|
||||
- 包含棋子列表、羁绊统计等信息
|
||||
- 提供阵容分析功能
|
||||
|
||||
### 使用示例
|
||||
|
||||
```python
|
||||
from src.data_provider import DataQueryAPI
|
||||
from src.recommendation import RecommendationEngine
|
||||
from src.scoring import TeamScorer
|
||||
|
||||
# 初始化API和推荐引擎
|
||||
api = DataQueryAPI()
|
||||
engine = RecommendationEngine(api=api)
|
||||
|
||||
# 指定人口推荐
|
||||
teams = engine.recommend_team(population=9)
|
||||
|
||||
# 指定羁绊推荐
|
||||
required_synergies = [
|
||||
{'name': '刺客', 'level': 4},
|
||||
{'name': '忍者', 'level': 1}
|
||||
]
|
||||
teams = engine.recommend_team(
|
||||
population=8,
|
||||
required_synergies=required_synergies
|
||||
)
|
||||
|
||||
# 指定棋子推荐
|
||||
required_chess = [
|
||||
{'name': '阿卡丽'},
|
||||
{'name': '劫'}
|
||||
]
|
||||
teams = engine.recommend_team(
|
||||
population=7,
|
||||
required_chess=required_chess
|
||||
)
|
||||
|
||||
# 查看推荐结果
|
||||
for team in teams:
|
||||
print(f"推荐阵容评分: {team.score}")
|
||||
print(f"阵容棋子: {[chess['displayName'] for chess in team.chess_list]}")
|
||||
```
|
||||
|
||||
## 阵容评分模块详解
|
||||
|
||||
阵容评分模块提供了对阵容进行综合评分的功能,考虑多个因素。
|
||||
|
||||
### 主要组件
|
||||
|
||||
1. **TeamScorer**: 阵容评分系统
|
||||
- 综合考虑羁绊数量、等级、棋子费用等因素
|
||||
- 提供可配置的评分权重
|
||||
- 多维度评估阵容强度
|
||||
|
||||
2. **ScoringConfig**: 评分配置类
|
||||
- 定义各项评分权重
|
||||
- 支持自定义评分策略
|
||||
|
||||
### 使用示例
|
||||
|
||||
```python
|
||||
from src.scoring import TeamScorer, ScoringConfig
|
||||
|
||||
# 创建自定义评分配置
|
||||
config = ScoringConfig(
|
||||
synergy_level_weight=1.2, # 增加羁绊等级权重
|
||||
chess_cost_weight=0.5 # 增加棋子费用权重
|
||||
)
|
||||
|
||||
# 初始化评分系统
|
||||
scorer = TeamScorer(config=config)
|
||||
|
||||
# 对阵容进行评分
|
||||
score = scorer.score_team(team)
|
||||
print(f"阵容评分: {score}")
|
||||
|
||||
# 动态调整评分参数
|
||||
scorer.customize_scoring(synergy_level_weight=1.5)
|
||||
```
|
||||
|
||||
## 开发计划
|
||||
|
||||
- [x] 数据提供模块
|
||||
- [ ] 阵容推荐模块
|
||||
- [ ] 阵容评分模块
|
||||
- [x] 阵容推荐模块
|
||||
- [x] 阵容评分模块
|
||||
- [ ] 接口模块
|
||||
- [ ] 图形用户界面
|
||||
|
||||
|
360
README.txt
360
README.txt
@ -1,360 +0,0 @@
|
||||
# 项目名称:云顶之弈阵容推荐器
|
||||
|
||||
## 概述
|
||||
|
||||
本项目旨在基于 Riot 官方提供的云顶之弈数据(包括职业、特质、棋子信息)实现自动化的阵容推荐。通过一系列可配置的评分机制,项目能够输出一套最优的阵容组合。本 README 将详细介绍项目功能、模块设计、开发环境、使用方法及开发要点,方便后续的代码实现与维护。
|
||||
|
||||
---
|
||||
|
||||
## 目标功能
|
||||
|
||||
1. **数据提供模块**
|
||||
|
||||
* 从本地或在线接口获取最新的「职业(job)」「特质(race)」「棋子(chess)」数据。
|
||||
* 提供统一的查询接口:
|
||||
|
||||
* 获取指定羁绊(职业/特质)的所有可用棋子
|
||||
* 获取某个棋子的所有羁绊
|
||||
* 获取所有羁绊列表
|
||||
* 获取某个棋子的详细信息
|
||||
* 获取某个羁绊的详细信息
|
||||
* 支持缓存与数据更新,确保系统在多次查询时有较快的响应速度。
|
||||
2. **阵容推荐模块**
|
||||
|
||||
* 根据指定的阵容需求(人口数量、必须包含的羁绊/棋子等)自动生成当前版本云顶之弈中合理的阵容。
|
||||
* 兼容多羁绊、多棋子等多种复杂输入,生成符合要求的多个可行阵容。
|
||||
* 将所有候选阵容进行分数计算并择优返回最高得分阵容。
|
||||
3. **阵容评分模块**
|
||||
|
||||
* 提供评分系统,对阵容进行综合打分:
|
||||
|
||||
* 考虑羁绊数量及羁绊等级(如:2重装、4重装、6重装等触发效果)。
|
||||
* 考虑棋子的费用、属性强度、技能等基础输出或耐久能力。
|
||||
* 提供默认评分权重(例如羁绊优先度、棋子星级、费用等),并允许自定义权重配置(通过配置文件覆盖默认值)。
|
||||
* 保证评分算法可扩展性,后续可集成更复杂的策略或外部数据(如胜率、大数据统计等)。
|
||||
4. **接口模块**
|
||||
|
||||
* 提供外部可调用的API,接收下列可能的输入条件:
|
||||
|
||||
* 指定人口(如 8 人口或 9 人口阵容)。
|
||||
* 指定必须包含的羁绊(一个或多个)。
|
||||
* 指定必须包含的棋子(一个或多个)。
|
||||
* 其它自定义约束条件(如费用上限、指定主C等)。
|
||||
* 调用数据提供模块和阵容推荐模块完成计算后,将结果(最佳阵容或多套可行阵容)返回给调用方。
|
||||
|
||||
---
|
||||
|
||||
## 环境和依赖
|
||||
|
||||
* **开发语言:** Python 3.8 或更高版本
|
||||
* **主要依赖库(示例):**
|
||||
|
||||
* `requests`:在线获取官方数据
|
||||
* `pandas`:数据处理库,用于数据筛选、分析等
|
||||
* `pyyaml`:配置文件解析库,用于加载自定义权重配置
|
||||
* 其他依赖请根据实际需求添加
|
||||
|
||||
安装依赖示例:
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 项目结构
|
||||
|
||||
下面是建议的项目目录结构,供参考和进一步修改:
|
||||
|
||||
```
|
||||
.
|
||||
├── data/
|
||||
│ ├── job.json # 职业数据(从官方api获取后存放本地)
|
||||
│ ├── race.json # 特质数据
|
||||
│ └── chess.json # 棋子数据
|
||||
├── config/
|
||||
│ └── score_config.yaml # (可选)自定义评分权重配置文件
|
||||
├── src/
|
||||
│ ├── data_provider/
|
||||
│ │ ├── data_loader.py # 从本地或在线获取数据的逻辑
|
||||
│ │ └── data_query_api.py # 提供对外的数据查询API
|
||||
│ ├── recommendation/
|
||||
│ │ └── composition_recommender.py # 阵容推荐模块核心逻辑
|
||||
│ ├── scoring/
|
||||
│ │ └── score_calculator.py # 阵容评分模块
|
||||
│ └── interface/
|
||||
│ └── api.py # 对外接口模块
|
||||
├── tests/
|
||||
│ ├── test_data_provider.py
|
||||
│ ├── test_recommendation.py
|
||||
│ ├── test_scoring.py
|
||||
│ └── test_api.py
|
||||
├── README.md
|
||||
└── requirements.txt
|
||||
```
|
||||
|
||||
各模块功能说明:
|
||||
|
||||
1. **`data_loader.py`**
|
||||
|
||||
* 负责从本地文件或在线接口获取最新的 job/race/chess 数据。
|
||||
* 每次启动时更新数据。
|
||||
* 在线接口:
|
||||
|
||||
* `'chess': 'https://game.gtimg.cn/images/lol/act/img/tft/js/chess.js'`
|
||||
* `'job': 'https://game.gtimg.cn/images/lol/act/img/tft/js/job.js'`
|
||||
* `'race': 'https://game.gtimg.cn/images/lol/act/img/tft/js/race.js'`
|
||||
2. **`data_query_api.py`**
|
||||
|
||||
* 读取 `data_loader` 载入的数据并对外提供查询功能:
|
||||
|
||||
* 获取指定羁绊下的所有棋子
|
||||
* 获取棋子的所有羁绊
|
||||
* 获取某个羁绊/棋子的详细信息
|
||||
* 其它辅助查询方法。
|
||||
3. **`composition_recommender.py`**
|
||||
|
||||
* 接收需求参数(如人口、羁绊、棋子)进行阵容搜索与组合。
|
||||
* 生成所有可能的组合后,调用 **评分模块** 对组合评分。
|
||||
* 推荐评分最高的一套或多套阵容输出。
|
||||
* 推荐逻辑可参考:
|
||||
|
||||
* *基于羁绊的优先级:* 每当阵容中某一羁绊人数接近下一等级阈值时,可优先考虑补充同羁绊棋子。
|
||||
* *基于棋子价值:* 根据棋子费用、技能强度等优先选择高价值棋子,但要平衡费用和羁绊需求。
|
||||
4. **`score_calculator.py`**
|
||||
|
||||
* 根据提供的评分配置(默认或自定义)对给定阵容进行评分:
|
||||
|
||||
* *羁绊评分:* 如 2重装、4重装、6重装等带来的收益加分。
|
||||
* *棋子评分:* 根据费用、技能伤害、攻防属性等进行综合评估。
|
||||
* *人口/星级加成:* 如果同一个棋子假设有星级等维度,也可作为额外加分点。
|
||||
* 如果有 `score_config.yaml`,则读取自定义权重覆盖默认值。
|
||||
* 返回阵容总得分,供推荐模块最终排序使用。
|
||||
5. **`api.py`**
|
||||
|
||||
* 提供对外可调用的API函数或 RESTful 接口:
|
||||
|
||||
* 接口示例:
|
||||
|
||||
```python
|
||||
def get_recommended_composition( population=8, required_syns=None, # 必须包含的羁绊 required_champs=None, # 必须包含的棋子 score_config_path=None): """ 外部可调用的主要函数: 1. 从 data_query_api 获取基础数据 2. 调用 composition_recommender 生成所有可行阵容 3. 调用 score_calculator 对阵容评分 4. 返回最优阵容 """ ...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 使用说明
|
||||
|
||||
1. **获取数据**
|
||||
|
||||
* 每次启动时,从官方接口获取数据,存放在内存缓存或本地文件中
|
||||
* 若数据获取失败,应用退出
|
||||
* 数据格式:
|
||||
|
||||
* job:
|
||||
|
||||
* ```json
|
||||
{
|
||||
"version": "15.7",
|
||||
"season": "2025.S14",
|
||||
"modeId": "1",
|
||||
"time": "2025-04-01 14:31:07",
|
||||
"data": [
|
||||
{
|
||||
"jobId": "10155",
|
||||
"name": "超频战士",
|
||||
"traitId": "10155",
|
||||
"introduce": "【超频战士】们可以通过该羁绊专有的【超频】属性来给他们的技能提供独特增强。",
|
||||
"alias": "10155.png",
|
||||
"level": {
|
||||
"2": "+1 【超频】属性, 100 生命值",
|
||||
"3": "+2 【超频】属性, 200 生命值",
|
||||
"4": "+3 【超频】属性, 350 生命值",
|
||||
"5": "+4 【超频】属性, 500 生命值"
|
||||
},
|
||||
"TFTID": "10155",
|
||||
"characterid": "TFT14_Supercharge",
|
||||
"id": "2623",
|
||||
"imagePath": "https://game.gtimg.cn/images/lol/act/img/tft/classes/10155.png",
|
||||
"job_color_list": "2:1,3:2,4:3,5:3"
|
||||
},
|
||||
{
|
||||
"jobId": "10157",
|
||||
"name": "重装战士",
|
||||
"traitId": "10157",
|
||||
"introduce": "【重装战士】们在有护盾时获得10%伤害减免。\r\n战斗开始时和50%生命值时:获得一部分最大生命值的护盾值,持续10秒。",
|
||||
"alias": "10157.png",
|
||||
"level": {
|
||||
"2": "16%最大生命值",
|
||||
"4": "32%最大生命值",
|
||||
"6": "40%最大生命值;在有护盾时获得16% 伤害减免"
|
||||
},
|
||||
"TFTID": "10157",
|
||||
"characterid": "TFT14_Vanguard",
|
||||
"id": "2624",
|
||||
"imagePath": "https://game.gtimg.cn/images/lol/act/img/tft/classes/10157.png",
|
||||
"job_color_list": "2:1,4:2,6:3"
|
||||
},
|
||||
|
||||
......
|
||||
```
|
||||
* chess:
|
||||
|
||||
* ```json
|
||||
{
|
||||
"version": "15.7",
|
||||
"season": "2025.S14",
|
||||
"modeId": "1",
|
||||
"time": "2025-04-01 14:31:07",
|
||||
"data": [
|
||||
{
|
||||
"raceId": "10153",
|
||||
"name": "圣灵使者",
|
||||
"traitId": "10153",
|
||||
"introduce": "【圣灵使者】弈子会为你的小队提供独特的属性,该加成会随着已登场的【圣灵使者】弈子数量而提升。\r\n【圣灵使者】弈子们获得双倍。",
|
||||
"alias": "10153.png",
|
||||
"level": {
|
||||
"1": "100% 加成。",
|
||||
"2": "110% 加成。",
|
||||
"3": "125% 加成。",
|
||||
"4": "140% 加成。",
|
||||
"5": "160% 加成。",
|
||||
"6": "180% 加成。",
|
||||
"7": "200% 加成。"
|
||||
},
|
||||
"TFTID": "10153",
|
||||
"characterid": "TFT14_Divinicorp",
|
||||
"id": "2727",
|
||||
"imagePath": "https://game.gtimg.cn/images/lol/act/img/tft/origins/10153.png",
|
||||
"race_color_list": "1:1,2:2,3:2,4:3,5:3,6:3,7:4"
|
||||
},
|
||||
{
|
||||
"raceId": "10154",
|
||||
"name": "街头恶魔",
|
||||
"traitId": "10154",
|
||||
"introduce": "在【彩绘格】中的友军将获得生命值、法术强度和攻击力。一些格子是【签名格】并多提供50%此加成。\r\n【街头恶魔】使所有此加成翻倍。",
|
||||
"alias": "10154.png",
|
||||
"level": {
|
||||
"3": "+6% 生命值,6 法术强度,6% 攻击力",
|
||||
"5": "+10% 生命值,10 法术强度,10% 攻击力",
|
||||
"7": "+6% 生命值,15 法术强度,15% 攻击力",
|
||||
"10": "+50% 生命值,50 法术强度,50% 攻击力,尽情绘画!"
|
||||
},
|
||||
"TFTID": "10154",
|
||||
"characterid": "TFT14_StreetDemon",
|
||||
"id": "2728",
|
||||
"imagePath": "https://game.gtimg.cn/images/lol/act/img/tft/origins/10154.png",
|
||||
"race_color_list": "3:1,5:2,7:3,10:4"
|
||||
},
|
||||
```
|
||||
* race:
|
||||
|
||||
* ```json
|
||||
{
|
||||
"version": "15.7",
|
||||
"season": "2025.S14",
|
||||
"modeId": "1",
|
||||
"time": "2025-04-01 14:31:07",
|
||||
"data": [
|
||||
{
|
||||
"raceId": "10153",
|
||||
"name": "圣灵使者",
|
||||
"traitId": "10153",
|
||||
"introduce": "【圣灵使者】弈子会为你的小队提供独特的属性,该加成会随着已登场的【圣灵使者】弈子数量而提升。\r\n【圣灵使者】弈子们获得双倍。",
|
||||
"alias": "10153.png",
|
||||
"level": {
|
||||
"1": "100% 加成。",
|
||||
"2": "110% 加成。",
|
||||
"3": "125% 加成。",
|
||||
"4": "140% 加成。",
|
||||
"5": "160% 加成。",
|
||||
"6": "180% 加成。",
|
||||
"7": "200% 加成。"
|
||||
},
|
||||
"TFTID": "10153",
|
||||
"characterid": "TFT14_Divinicorp",
|
||||
"id": "2727",
|
||||
"imagePath": "https://game.gtimg.cn/images/lol/act/img/tft/origins/10153.png",
|
||||
"race_color_list": "1:1,2:2,3:2,4:3,5:3,6:3,7:4"
|
||||
},
|
||||
{
|
||||
"raceId": "10154",
|
||||
"name": "街头恶魔",
|
||||
"traitId": "10154",
|
||||
"introduce": "在【彩绘格】中的友军将获得生命值、法术强度和攻击力。一些格子是【签名格】并多提供50%此加成。\r\n【街头恶魔】使所有此加成翻倍。",
|
||||
"alias": "10154.png",
|
||||
"level": {
|
||||
"3": "+6% 生命值,6 法术强度,6% 攻击力",
|
||||
"5": "+10% 生命值,10 法术强度,10% 攻击力",
|
||||
"7": "+6% 生命值,15 法术强度,15% 攻击力",
|
||||
"10": "+50% 生命值,50 法术强度,50% 攻击力,尽情绘画!"
|
||||
},
|
||||
"TFTID": "10154",
|
||||
"characterid": "TFT14_StreetDemon",
|
||||
"id": "2728",
|
||||
"imagePath": "https://game.gtimg.cn/images/lol/act/img/tft/origins/10154.png",
|
||||
"race_color_list": "3:1,5:2,7:3,10:4"
|
||||
},
|
||||
```
|
||||
2. **定制评分**
|
||||
|
||||
* 在 `config/score_config.yaml` 文件中写入自定义的评分权重,例如:
|
||||
|
||||
```yaml
|
||||
synergy_weight: base: 1.0 重装战士: 1.2 # 重装战士特殊加成 ...champion_weight: base_price_factor: 0.5 ...
|
||||
```
|
||||
* 也可直接在 `score_calculator.py` 模块中写死默认权重,不使用配置文件。
|
||||
3. **调用推荐接口**
|
||||
|
||||
* 在命令行 / 脚本中:
|
||||
|
||||
```python
|
||||
from src.interface.api import get_recommended_compositionbest_comp = get_recommended_composition( population=8, required_syns=['重装战士', '街头恶魔'], required_champs=['布兰德'], score_config_path='config/score_config.yaml')print(best_comp)
|
||||
```
|
||||
* 如果实现了网页接口或CLI,可直接通过相应方式进行调用。
|
||||
4. **扩展性**
|
||||
|
||||
* 通过新增或修改数据文件,可无缝整合新版本羁绊、棋子数据。
|
||||
* 评分系统可根据策略和经验进行不断迭代。
|
||||
|
||||
---
|
||||
|
||||
## 测试
|
||||
|
||||
* 建议使用 `pytest` 或 `unittest` 进行测试:
|
||||
|
||||
* `test_data_provider.py`:测试数据加载和查询的正确性。
|
||||
* `test_recommendation.py`:测试阵容推荐逻辑在各种输入条件下是否能正确生成可行阵容。
|
||||
* `test_scoring.py`:测试评分结果是否符合预期(含默认权重与自定义权重)。
|
||||
* `test_api.py`:测试对外接口的可用性和稳定性。
|
||||
|
||||
---
|
||||
|
||||
## 深度思考与注意事项
|
||||
|
||||
1. **数据版本与兼容性**
|
||||
|
||||
* 云顶之弈更新频繁,羁绊及棋子经常变动。为保证兼容性和可持续维护,需要在数据模块中预留多版本并行处理或自动更新的机制。
|
||||
* 可以在数据库或文件系统中记录不同版本的 json 数据,并通过接口指定使用哪个版本。
|
||||
2. **羁绊冲突与优先级**
|
||||
|
||||
* 部分羁绊可能存在冲突(如同时带来的属性相互覆盖)或并不兼容,需要在推荐阵容时自行过滤或降低评分。
|
||||
* 在评分系统中,应设定羁绊上限规则(如某些羁绊过量堆叠收益递减)。
|
||||
3. **棋子升星问题**
|
||||
|
||||
* 不考虑棋子升星问题
|
||||
4. **性能**
|
||||
|
||||
* 若人口和可选棋子规模较大,暴力穷举组合的时间开销可能很高,需要考虑使用回溯、剪枝、启发式算法、或动态规划方式加速推荐过程。
|
||||
5. **配置管理**
|
||||
|
||||
* 对于多种参数(如羁绊优先级、棋子费用优先级、特定羁绊的特殊加成等),建议使用统一配置文件管理,减少魔法值散落于代码。
|
||||
* 同时要注意不同格式(JSON/YAML/INI)的使用,保持一致性。
|
||||
|
||||
---
|
||||
|
||||
## 结语
|
||||
|
||||
此项目聚焦于云顶之弈的阵容搭配与推荐,核心思路是利用官方提供的职业(job)、特质(race)及棋子(chess)数据,通过一套灵活可扩展的评分系统,为特定版本提供自动化、高质量的阵容建议。
|
||||
如需进一步扩展,也可对接大数据平台,采集高分段玩家的对局数据,构建更准确的胜率模型和评分模型,从而不断优化 推荐器 的效果。
|
||||
|
||||
若有任何疑问或改进建议,欢迎在项目中提出 Issue 或 Pull Request,让我们一起将该项目做得更完善、易用、智能。祝各位在云顶之弈的旅程中诸神之巅!
|
9
main.py
9
main.py
@ -4,6 +4,7 @@
|
||||
"""
|
||||
import sys
|
||||
from src.data_provider_demo import main as data_provider_demo
|
||||
from src.recommendation_demo import main as recommendation_demo
|
||||
|
||||
|
||||
def main():
|
||||
@ -14,11 +15,16 @@ def main():
|
||||
if command == "data":
|
||||
# 运行数据提供模块演示
|
||||
data_provider_demo()
|
||||
elif command == "recommend":
|
||||
# 运行阵容推荐模块演示
|
||||
recommendation_demo()
|
||||
else:
|
||||
print(f"未知命令: {command}")
|
||||
print_usage()
|
||||
else:
|
||||
# 默认运行数据提供模块演示
|
||||
print_usage()
|
||||
print("\n默认运行数据提供模块演示...\n")
|
||||
data_provider_demo()
|
||||
|
||||
|
||||
@ -26,7 +32,8 @@ def print_usage():
|
||||
"""打印使用帮助"""
|
||||
print("使用方法: python main.py [命令]")
|
||||
print("命令:")
|
||||
print(" data 运行数据提供模块演示")
|
||||
print(" data 运行数据提供模块演示")
|
||||
print(" recommend 运行阵容推荐模块演示")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
@ -1,4 +1,6 @@
|
||||
requests>=2.25.0
|
||||
pandas>=1.3.0
|
||||
pyyaml>=6.0
|
||||
pytest>=7.0.0
|
||||
pytest>=7.0.0
|
||||
numpy>=1.20.0
|
||||
matplotlib>=3.4.0
|
@ -1,3 +1,6 @@
|
||||
"""
|
||||
阵容推荐模块
|
||||
"""
|
||||
"""
|
||||
from .recommendation_engine import RecommendationEngine, TeamComposition
|
||||
|
||||
__all__ = ['RecommendationEngine', 'TeamComposition']
|
396
src/recommendation/recommendation_engine.py
Normal file
396
src/recommendation/recommendation_engine.py
Normal file
@ -0,0 +1,396 @@
|
||||
"""
|
||||
阵容推荐引擎 - 负责根据用户需求生成最优阵容
|
||||
|
||||
此模块实现了阵容推荐的核心逻辑,包括:
|
||||
1. 基于羁绊、棋子和人口约束生成可行阵容
|
||||
2. 根据评分标准对阵容进行排序
|
||||
3. 返回最优阵容推荐结果
|
||||
"""
|
||||
from typing import Dict, List, Optional, Any, Set, Tuple, Union
|
||||
import logging
|
||||
import itertools
|
||||
from dataclasses import dataclass, field
|
||||
from src.data_provider import DataQueryAPI
|
||||
from src.scoring.scoring_system import TeamScorer
|
||||
|
||||
# 配置日志
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
||||
)
|
||||
logger = logging.getLogger("TFT-Strategist-RecommendationEngine")
|
||||
|
||||
@dataclass
|
||||
class TeamComposition:
|
||||
"""表示一个云顶之弈阵容"""
|
||||
chess_list: List[Dict[str, Any]] = field(default_factory=list)
|
||||
synergy_counts: Dict[str, int] = field(default_factory=dict)
|
||||
synergy_levels: Dict[str, List[Dict[str, Any]]] = field(default_factory=dict)
|
||||
total_cost: int = 0
|
||||
score: float = 0.0
|
||||
|
||||
@property
|
||||
def size(self) -> int:
|
||||
"""阵容人口数"""
|
||||
return len(self.chess_list)
|
||||
|
||||
def add_chess(self, chess: Dict[str, Any]) -> None:
|
||||
"""添加棋子到阵容"""
|
||||
if chess not in self.chess_list:
|
||||
self.chess_list.append(chess)
|
||||
self.total_cost += int(chess.get('price', 0))
|
||||
# 更新羁绊计数
|
||||
|
||||
def calculate_synergies(self, api: DataQueryAPI) -> None:
|
||||
"""计算阵容的所有羁绊及其激活等级"""
|
||||
self.synergy_counts = {}
|
||||
self.synergy_levels = {'job': [], 'race': []}
|
||||
|
||||
# 统计所有羁绊的数量
|
||||
for chess in self.chess_list:
|
||||
# 处理职业
|
||||
for job_id in chess.get('jobIds', '').split(','):
|
||||
if job_id:
|
||||
self.synergy_counts[job_id] = self.synergy_counts.get(job_id, 0) + 1
|
||||
|
||||
# 处理特质
|
||||
for race_id in chess.get('raceIds', '').split(','):
|
||||
if race_id:
|
||||
self.synergy_counts[race_id] = self.synergy_counts.get(race_id, 0) + 1
|
||||
|
||||
# 确定各羁绊激活的等级
|
||||
for synergy_id, count in self.synergy_counts.items():
|
||||
synergy = api.get_synergy_by_id(synergy_id)
|
||||
if not synergy:
|
||||
continue
|
||||
|
||||
# 确定激活的等级
|
||||
levels = api.get_synergy_levels(synergy_id)
|
||||
active_levels = []
|
||||
|
||||
for level_str, effect in levels.items():
|
||||
level = int(level_str)
|
||||
if count >= level:
|
||||
active_level = {
|
||||
'id': synergy_id,
|
||||
'name': synergy.get('name', ''),
|
||||
'level': level,
|
||||
'count': count,
|
||||
'effect': effect
|
||||
}
|
||||
active_levels.append(active_level)
|
||||
|
||||
if active_levels:
|
||||
# 按等级排序
|
||||
active_levels.sort(key=lambda x: x['level'])
|
||||
# 将羁绊归类为职业或特质
|
||||
if 'jobId' in synergy:
|
||||
self.synergy_levels['job'].extend(active_levels)
|
||||
else:
|
||||
self.synergy_levels['race'].extend(active_levels)
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""将阵容转换为字典表示"""
|
||||
return {
|
||||
'chess_list': self.chess_list,
|
||||
'synergy_counts': self.synergy_counts,
|
||||
'synergy_levels': self.synergy_levels,
|
||||
'total_cost': self.total_cost,
|
||||
'size': self.size,
|
||||
'score': self.score
|
||||
}
|
||||
|
||||
|
||||
class RecommendationEngine:
|
||||
"""
|
||||
阵容推荐引擎,负责根据用户需求生成最优阵容
|
||||
"""
|
||||
|
||||
def __init__(self, api: Optional[DataQueryAPI] = None, scorer: Optional[TeamScorer] = None):
|
||||
"""
|
||||
初始化阵容推荐引擎
|
||||
|
||||
Args:
|
||||
api: 数据查询API实例,如果为None则创建一个新的实例
|
||||
scorer: 阵容评分系统实例,如果为None则创建一个新的实例
|
||||
"""
|
||||
self.api = api if api else DataQueryAPI()
|
||||
self.scorer = scorer if scorer else TeamScorer()
|
||||
|
||||
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[TeamComposition]:
|
||||
"""
|
||||
根据用户需求推荐阵容
|
||||
|
||||
Args:
|
||||
population: 阵容人口数,默认为9
|
||||
required_synergies: 必须包含的羁绊列表,每个羁绊为一个字典,包含id和最低激活等级
|
||||
required_chess: 必须包含的棋子列表
|
||||
max_results: 最多返回的推荐阵容数量
|
||||
|
||||
Returns:
|
||||
List[TeamComposition]: 推荐的阵容列表,按评分从高到低排序
|
||||
"""
|
||||
if population < 1 or population > 10:
|
||||
logger.warning(f"无效的人口数 {population},已调整为默认值9")
|
||||
population = 9
|
||||
|
||||
required_synergies = required_synergies or []
|
||||
required_chess = required_chess or []
|
||||
|
||||
# 1. 处理必选棋子
|
||||
base_team = TeamComposition()
|
||||
for chess_info in required_chess:
|
||||
if isinstance(chess_info, dict) and 'name' in chess_info:
|
||||
chess = self.api.get_chess_by_name(chess_info['name'])
|
||||
elif isinstance(chess_info, dict) and 'id' in chess_info:
|
||||
chess = self.api.get_chess_by_id(chess_info['id'])
|
||||
else:
|
||||
chess = self.api.get_chess_by_name(str(chess_info))
|
||||
|
||||
if chess:
|
||||
base_team.add_chess(chess)
|
||||
else:
|
||||
logger.warning(f"未找到棋子: {chess_info}")
|
||||
|
||||
# 如果必选棋子已经超过了人口限制,则直接返回
|
||||
if base_team.size > population:
|
||||
logger.warning(f"必选棋子数量({base_team.size})超过了人口限制({population})")
|
||||
base_team.calculate_synergies(self.api)
|
||||
base_team.score = self.scorer.score_team(base_team)
|
||||
return [base_team]
|
||||
|
||||
# 2. 获取羁绊所需的棋子集合
|
||||
synergy_chess_sets = []
|
||||
for synergy_info in required_synergies:
|
||||
if isinstance(synergy_info, dict) and 'name' in synergy_info:
|
||||
synergy = self.api.get_synergy_by_name(synergy_info['name'])
|
||||
min_level = synergy_info.get('level', 1)
|
||||
elif isinstance(synergy_info, dict) and 'id' in synergy_info:
|
||||
synergy = self.api.get_synergy_by_id(synergy_info['id'])
|
||||
min_level = synergy_info.get('level', 1)
|
||||
else:
|
||||
synergy = self.api.get_synergy_by_name(str(synergy_info))
|
||||
min_level = 1
|
||||
|
||||
if not synergy:
|
||||
logger.warning(f"未找到羁绊: {synergy_info}")
|
||||
continue
|
||||
|
||||
synergy_id = synergy.get('jobId') or synergy.get('raceId')
|
||||
chess_list = self.api.get_chess_by_synergy(synergy_id)
|
||||
|
||||
# 获取该羁绊的激活等级
|
||||
levels = self.api.get_synergy_levels(synergy_id)
|
||||
target_level = 0
|
||||
for level_str in levels.keys():
|
||||
level = int(level_str)
|
||||
if level >= min_level:
|
||||
target_level = level
|
||||
break
|
||||
|
||||
if target_level == 0:
|
||||
target_level = min_level
|
||||
|
||||
synergy_chess_sets.append({
|
||||
'synergy': synergy,
|
||||
'chess_list': chess_list,
|
||||
'target_level': target_level
|
||||
})
|
||||
|
||||
# 3. 生成候选阵容
|
||||
candidate_teams = self._generate_candidate_teams(
|
||||
base_team, synergy_chess_sets, population
|
||||
)
|
||||
|
||||
# 4. 评分排序并返回结果
|
||||
for team in candidate_teams:
|
||||
team.calculate_synergies(self.api)
|
||||
team.score = self.scorer.score_team(team)
|
||||
|
||||
# 按评分从高到低排序
|
||||
candidate_teams.sort(key=lambda x: x.score, reverse=True)
|
||||
|
||||
return candidate_teams[:max_results]
|
||||
|
||||
def _generate_candidate_teams(
|
||||
self,
|
||||
base_team: TeamComposition,
|
||||
synergy_chess_sets: List[Dict[str, Any]],
|
||||
population: int
|
||||
) -> List[TeamComposition]:
|
||||
"""
|
||||
生成候选阵容
|
||||
|
||||
Args:
|
||||
base_team: 基础阵容,包含必选棋子
|
||||
synergy_chess_sets: 各个必选羁绊的棋子集合
|
||||
population: 人口限制
|
||||
|
||||
Returns:
|
||||
List[TeamComposition]: 候选阵容列表
|
||||
"""
|
||||
# 已经有基础棋子
|
||||
remaining_slots = population - base_team.size
|
||||
|
||||
# 如果没有必选羁绊,则直接从所有棋子中选择
|
||||
if not synergy_chess_sets:
|
||||
return self._fill_team_with_best_chess(base_team, remaining_slots)
|
||||
|
||||
# 处理必选羁绊
|
||||
candidate_teams = []
|
||||
|
||||
# 为每个羁绊选择合适的棋子
|
||||
synergy_combinations = []
|
||||
for synergy_set in synergy_chess_sets:
|
||||
synergy = synergy_set['synergy']
|
||||
chess_list = synergy_set['chess_list']
|
||||
target_level = synergy_set['target_level']
|
||||
|
||||
# 过滤掉已经在基础阵容中的棋子
|
||||
available_chess = [
|
||||
chess for chess in chess_list
|
||||
if chess not in base_team.chess_list
|
||||
]
|
||||
|
||||
# 计算还需要多少个该羁绊的棋子
|
||||
synergy_id = synergy.get('jobId') or synergy.get('raceId')
|
||||
|
||||
# 统计基础阵容中有多少个该羁绊的棋子
|
||||
base_count = 0
|
||||
for chess in base_team.chess_list:
|
||||
job_ids = chess.get('jobIds', '').split(',')
|
||||
race_ids = chess.get('raceIds', '').split(',')
|
||||
if synergy_id in job_ids or synergy_id in race_ids:
|
||||
base_count += 1
|
||||
|
||||
# 还需要的棋子数量
|
||||
needed_count = max(0, target_level - base_count)
|
||||
|
||||
# 如果该羁绊已经满足要求,跳过
|
||||
if needed_count == 0:
|
||||
continue
|
||||
|
||||
# 如果可用棋子不足,则尽可能多选
|
||||
if len(available_chess) < needed_count:
|
||||
needed_count = len(available_chess)
|
||||
|
||||
# 生成该羁绊的所有可能组合
|
||||
combinations = list(itertools.combinations(available_chess, needed_count))
|
||||
synergy_combinations.append(combinations)
|
||||
|
||||
# 如果没有有效的羁绊组合,则直接填充最好的棋子
|
||||
if not synergy_combinations:
|
||||
return self._fill_team_with_best_chess(base_team, remaining_slots)
|
||||
|
||||
# 生成所有羁绊组合的笛卡尔积
|
||||
for combo_product in itertools.product(*synergy_combinations):
|
||||
# 扁平化组合成一个列表
|
||||
flat_combo = []
|
||||
for combo in combo_product:
|
||||
flat_combo.extend(combo)
|
||||
|
||||
# 去重
|
||||
unique_combo = []
|
||||
for chess in flat_combo:
|
||||
if chess not in unique_combo:
|
||||
unique_combo.append(chess)
|
||||
|
||||
# 如果组合后的棋子数量超过剩余槽位,跳过
|
||||
if len(unique_combo) > remaining_slots:
|
||||
continue
|
||||
|
||||
# 创建新的阵容
|
||||
new_team = TeamComposition()
|
||||
for chess in base_team.chess_list:
|
||||
new_team.add_chess(chess)
|
||||
|
||||
# 添加羁绊棋子
|
||||
for chess in unique_combo:
|
||||
new_team.add_chess(chess)
|
||||
|
||||
# 如果还有剩余槽位,用最佳棋子填充
|
||||
if new_team.size < population:
|
||||
new_remaining_slots = population - new_team.size
|
||||
filled_teams = self._fill_team_with_best_chess(new_team, new_remaining_slots)
|
||||
if filled_teams:
|
||||
candidate_teams.extend(filled_teams)
|
||||
else:
|
||||
candidate_teams.append(new_team)
|
||||
|
||||
# 如果没有生成候选阵容,直接填充最好的棋子
|
||||
if not candidate_teams:
|
||||
return self._fill_team_with_best_chess(base_team, remaining_slots)
|
||||
|
||||
return candidate_teams
|
||||
|
||||
def _fill_team_with_best_chess(
|
||||
self,
|
||||
base_team: TeamComposition,
|
||||
remaining_slots: int
|
||||
) -> List[TeamComposition]:
|
||||
"""
|
||||
用最佳棋子填充阵容剩余槽位
|
||||
|
||||
Args:
|
||||
base_team: 基础阵容
|
||||
remaining_slots: 剩余槽位数量
|
||||
|
||||
Returns:
|
||||
List[TeamComposition]: 填充后的阵容列表
|
||||
"""
|
||||
if remaining_slots <= 0:
|
||||
return [base_team]
|
||||
|
||||
# 获取所有可用棋子
|
||||
all_chess = self.api.get_all_chess()
|
||||
|
||||
# 过滤掉已经在基础阵容中的棋子
|
||||
available_chess = [
|
||||
chess for chess in all_chess
|
||||
if chess not in base_team.chess_list
|
||||
]
|
||||
|
||||
# 按照一定策略选择最佳棋子
|
||||
# 这里可以实现多种策略,如优先选择与现有阵容羁绊匹配的棋子、高费棋子等
|
||||
# 简单起见,我们用一个贪心策略:计算每个棋子能为阵容增加的羁绊收益
|
||||
|
||||
# 先计算基础阵容的羁绊情况
|
||||
base_team_copy = TeamComposition()
|
||||
for chess in base_team.chess_list:
|
||||
base_team_copy.add_chess(chess)
|
||||
base_team_copy.calculate_synergies(self.api)
|
||||
|
||||
# 评估每个棋子的收益
|
||||
chess_benefits = []
|
||||
for chess in available_chess:
|
||||
# 创建临时阵容
|
||||
temp_team = TeamComposition()
|
||||
for c in base_team.chess_list:
|
||||
temp_team.add_chess(c)
|
||||
temp_team.add_chess(chess)
|
||||
temp_team.calculate_synergies(self.api)
|
||||
|
||||
# 计算羁绊增益
|
||||
benefit = self.scorer.score_team(temp_team) - self.scorer.score_team(base_team_copy)
|
||||
chess_benefits.append((chess, benefit))
|
||||
|
||||
# 按收益排序
|
||||
chess_benefits.sort(key=lambda x: x[1], reverse=True)
|
||||
|
||||
# 选择前N个最佳棋子
|
||||
best_chess = [item[0] for item in chess_benefits[:remaining_slots]]
|
||||
|
||||
# 创建填充后的阵容
|
||||
filled_team = TeamComposition()
|
||||
for chess in base_team.chess_list:
|
||||
filled_team.add_chess(chess)
|
||||
for chess in best_chess:
|
||||
filled_team.add_chess(chess)
|
||||
|
||||
return [filled_team]
|
211
src/recommendation_demo.py
Normal file
211
src/recommendation_demo.py
Normal file
@ -0,0 +1,211 @@
|
||||
"""
|
||||
阵容推荐模块演示脚本
|
||||
"""
|
||||
from src.data_provider import DataQueryAPI
|
||||
from src.recommendation import RecommendationEngine
|
||||
from src.scoring import TeamScorer, ScoringConfig
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("=== 云顶之弈阵容推荐模块演示 ===")
|
||||
|
||||
# 初始化数据查询API
|
||||
print("\n正在加载数据...")
|
||||
api = DataQueryAPI()
|
||||
|
||||
# 初始化评分系统和推荐引擎
|
||||
scoring_config = ScoringConfig()
|
||||
scorer = TeamScorer(config=scoring_config)
|
||||
engine = RecommendationEngine(api=api, scorer=scorer)
|
||||
|
||||
# 显示数据基本信息
|
||||
print(f"\n当前游戏版本: {api.data_loader.get_latest_version()}")
|
||||
print(f"职业数量: {len(api.get_all_jobs())}")
|
||||
print(f"特质数量: {len(api.get_all_races())}")
|
||||
print(f"棋子数量: {len(api.get_all_chess())}")
|
||||
|
||||
# 互动推荐示例
|
||||
while True:
|
||||
print("\n请选择推荐方式:")
|
||||
print("1. 指定人口数量")
|
||||
print("2. 指定必选羁绊")
|
||||
print("3. 指定必选棋子")
|
||||
print("4. 综合条件推荐")
|
||||
print("5. 退出")
|
||||
|
||||
choice = input("请输入选项 (1-5): ")
|
||||
|
||||
if choice == '1':
|
||||
try:
|
||||
population = int(input("请输入阵容人口数 (1-10): "))
|
||||
if population < 1 or population > 10:
|
||||
print("无效的人口数,请输入1-10之间的整数")
|
||||
continue
|
||||
|
||||
print(f"\n正在生成{population}人口的最佳阵容...")
|
||||
teams = engine.recommend_team(population=population)
|
||||
_display_recommended_teams(teams)
|
||||
except ValueError:
|
||||
print("请输入有效的整数")
|
||||
|
||||
elif choice == '2':
|
||||
synergy_name = input("请输入羁绊名称: ")
|
||||
synergy = api.get_synergy_by_name(synergy_name)
|
||||
if not synergy:
|
||||
print(f"未找到名为 '{synergy_name}' 的羁绊")
|
||||
continue
|
||||
|
||||
try:
|
||||
level = int(input(f"请输入 {synergy_name} 的目标等级 (默认为1): ") or "1")
|
||||
required_synergies = [{'name': synergy_name, 'level': level}]
|
||||
|
||||
population = int(input("请输入阵容人口数 (1-10,默认为9): ") or "9")
|
||||
if population < 1 or population > 10:
|
||||
print("无效的人口数,已调整为默认值9")
|
||||
population = 9
|
||||
|
||||
print(f"\n正在生成包含 {synergy_name} ({level}级) 的{population}人口阵容...")
|
||||
teams = engine.recommend_team(
|
||||
population=population,
|
||||
required_synergies=required_synergies
|
||||
)
|
||||
_display_recommended_teams(teams)
|
||||
except ValueError:
|
||||
print("请输入有效的整数")
|
||||
|
||||
elif choice == '3':
|
||||
chess_name = input("请输入棋子名称: ")
|
||||
chess = api.get_chess_by_name(chess_name)
|
||||
if not chess:
|
||||
print(f"未找到名为 '{chess_name}' 的棋子")
|
||||
continue
|
||||
|
||||
try:
|
||||
population = int(input("请输入阵容人口数 (1-10,默认为9): ") or "9")
|
||||
if population < 1 or population > 10:
|
||||
print("无效的人口数,已调整为默认值9")
|
||||
population = 9
|
||||
|
||||
required_chess = [{'name': chess_name}]
|
||||
|
||||
print(f"\n正在生成包含 {chess_name} 的{population}人口阵容...")
|
||||
teams = engine.recommend_team(
|
||||
population=population,
|
||||
required_chess=required_chess
|
||||
)
|
||||
_display_recommended_teams(teams)
|
||||
except ValueError:
|
||||
print("请输入有效的整数")
|
||||
|
||||
elif choice == '4':
|
||||
try:
|
||||
# 收集必选羁绊
|
||||
required_synergies = []
|
||||
while True:
|
||||
synergy_name = input("请输入必选羁绊名称 (留空结束): ")
|
||||
if not synergy_name:
|
||||
break
|
||||
|
||||
synergy = api.get_synergy_by_name(synergy_name)
|
||||
if not synergy:
|
||||
print(f"未找到名为 '{synergy_name}' 的羁绊")
|
||||
continue
|
||||
|
||||
level = int(input(f"请输入 {synergy_name} 的目标等级 (默认为1): ") or "1")
|
||||
required_synergies.append({'name': synergy_name, 'level': level})
|
||||
|
||||
# 收集必选棋子
|
||||
required_chess = []
|
||||
while True:
|
||||
chess_name = input("请输入必选棋子名称 (留空结束): ")
|
||||
if not chess_name:
|
||||
break
|
||||
|
||||
chess = api.get_chess_by_name(chess_name)
|
||||
if not chess:
|
||||
print(f"未找到名为 '{chess_name}' 的棋子")
|
||||
continue
|
||||
|
||||
required_chess.append({'name': chess_name})
|
||||
|
||||
# 设置人口
|
||||
population = int(input("请输入阵容人口数 (1-10,默认为9): ") or "9")
|
||||
if population < 1 or population > 10:
|
||||
print("无效的人口数,已调整为默认值9")
|
||||
population = 9
|
||||
|
||||
# 生成推荐阵容
|
||||
print("\n正在生成符合条件的阵容...")
|
||||
teams = engine.recommend_team(
|
||||
population=population,
|
||||
required_synergies=required_synergies,
|
||||
required_chess=required_chess
|
||||
)
|
||||
_display_recommended_teams(teams)
|
||||
except ValueError:
|
||||
print("请输入有效的整数")
|
||||
|
||||
elif choice == '5':
|
||||
break
|
||||
|
||||
else:
|
||||
print("无效选项,请重新输入")
|
||||
|
||||
|
||||
def _display_recommended_teams(teams):
|
||||
"""显示推荐的阵容"""
|
||||
if not teams:
|
||||
print("未找到符合条件的阵容")
|
||||
return
|
||||
|
||||
print(f"\n找到 {len(teams)} 个推荐阵容:")
|
||||
for i, team in enumerate(teams, 1):
|
||||
print(f"\n=== 推荐阵容 {i} (评分: {team.score:.2f}) ===")
|
||||
|
||||
# 显示激活的羁绊
|
||||
print("\n激活的羁绊:")
|
||||
# 按照等级从高到低排序
|
||||
all_synergies = []
|
||||
for category in ['job', 'race']:
|
||||
for synergy in team.synergy_levels.get(category, []):
|
||||
all_synergies.append(synergy)
|
||||
|
||||
all_synergies.sort(key=lambda x: (x['count'], x['level']), reverse=True)
|
||||
|
||||
for synergy in all_synergies:
|
||||
print(f" - {synergy['name']} ({synergy['count']}/{synergy['level']}): {synergy['effect']}")
|
||||
|
||||
# 显示棋子列表
|
||||
print("\n棋子列表:")
|
||||
# 按照费用从高到低排序
|
||||
sorted_chess = sorted(team.chess_list, key=lambda x: int(x.get('price', '1')), reverse=True)
|
||||
for chess in sorted_chess:
|
||||
price = chess.get('price', '未知')
|
||||
# 获取棋子的羁绊
|
||||
job_ids = chess.get('jobIds', '').split(',')
|
||||
race_ids = chess.get('raceIds', '').split(',')
|
||||
|
||||
job_names = []
|
||||
for job_id in job_ids:
|
||||
if job_id:
|
||||
job = next((j for j in team.synergy_levels.get('job', []) if j['id'] == job_id), None)
|
||||
if job:
|
||||
job_names.append(job['name'])
|
||||
|
||||
race_names = []
|
||||
for race_id in race_ids:
|
||||
if race_id:
|
||||
race = next((r for r in team.synergy_levels.get('race', []) if r['id'] == race_id), None)
|
||||
if race:
|
||||
race_names.append(race['name'])
|
||||
|
||||
synergy_text = ' / '.join(job_names + race_names)
|
||||
print(f" - {chess['displayName']} ({price}费): {synergy_text}")
|
||||
|
||||
# 显示阵容总费用
|
||||
print(f"\n阵容总费用: {team.total_cost}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,3 +1,6 @@
|
||||
"""
|
||||
阵容评分模块
|
||||
"""
|
||||
"""
|
||||
from .scoring_system import TeamScorer, ScoringConfig
|
||||
|
||||
__all__ = ['TeamScorer', 'ScoringConfig']
|
198
src/scoring/scoring_system.py
Normal file
198
src/scoring/scoring_system.py
Normal file
@ -0,0 +1,198 @@
|
||||
"""
|
||||
阵容评分系统 - 负责对阵容进行综合评分
|
||||
|
||||
此模块提供了对云顶之弈阵容的评分功能,考虑多个因素:
|
||||
1. 羁绊数量及等级
|
||||
2. 羁绊协同性
|
||||
3. 棋子费用分布
|
||||
4. 阵容整体强度
|
||||
"""
|
||||
from typing import Dict, List, Any, Optional
|
||||
import logging
|
||||
from dataclasses import dataclass
|
||||
|
||||
# 配置日志
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
||||
)
|
||||
logger = logging.getLogger("TFT-Strategist-ScoringSystem")
|
||||
|
||||
@dataclass
|
||||
class ScoringConfig:
|
||||
"""阵容评分配置"""
|
||||
# 基础权重
|
||||
synergy_level_weight: float = 1.0 # 羁绊等级权重
|
||||
synergy_count_weight: float = 0.5 # 羁绊种类数量权重
|
||||
chess_cost_weight: float = 0.1 # 棋子费用权重
|
||||
|
||||
# 羁绊等级权重倍数
|
||||
level_multipliers: Dict[int, float] = None
|
||||
|
||||
# 棋子费用权重倍数
|
||||
cost_multipliers: Dict[str, float] = None
|
||||
|
||||
def __post_init__(self):
|
||||
"""初始化默认值"""
|
||||
if self.level_multipliers is None:
|
||||
# 羁绊等级越高,权重越大
|
||||
self.level_multipliers = {
|
||||
1: 1.0,
|
||||
2: 1.5,
|
||||
3: 2.0,
|
||||
4: 3.0,
|
||||
5: 4.0,
|
||||
6: 5.0,
|
||||
7: 6.0,
|
||||
8: 7.0,
|
||||
9: 8.0
|
||||
}
|
||||
|
||||
if self.cost_multipliers is None:
|
||||
# 棋子费用越高,权重越大
|
||||
self.cost_multipliers = {
|
||||
'1': 1.0,
|
||||
'2': 1.5,
|
||||
'3': 2.0,
|
||||
'4': 3.0,
|
||||
'5': 4.0
|
||||
}
|
||||
|
||||
|
||||
class TeamScorer:
|
||||
"""
|
||||
阵容评分系统,负责对阵容进行综合评分
|
||||
"""
|
||||
|
||||
def __init__(self, config: Optional[ScoringConfig] = None):
|
||||
"""
|
||||
初始化阵容评分系统
|
||||
|
||||
Args:
|
||||
config: 评分配置,如果为None则使用默认配置
|
||||
"""
|
||||
self.config = config if config else ScoringConfig()
|
||||
|
||||
def score_team(self, team) -> float:
|
||||
"""
|
||||
对阵容进行综合评分
|
||||
|
||||
Args:
|
||||
team: TeamComposition实例
|
||||
|
||||
Returns:
|
||||
float: 评分结果
|
||||
"""
|
||||
# 1. 羁绊等级评分
|
||||
synergy_level_score = self._score_synergy_levels(team)
|
||||
|
||||
# 2. 羁绊种类数量评分
|
||||
synergy_count_score = self._score_synergy_count(team)
|
||||
|
||||
# 3. 棋子费用评分
|
||||
chess_cost_score = self._score_chess_cost(team)
|
||||
|
||||
# 4. 阵容整体评分
|
||||
# 综合以上三项,加权求和
|
||||
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
|
||||
)
|
||||
|
||||
return total_score
|
||||
|
||||
def _score_synergy_levels(self, team) -> float:
|
||||
"""
|
||||
评分羁绊等级
|
||||
|
||||
Args:
|
||||
team: TeamComposition实例
|
||||
|
||||
Returns:
|
||||
float: 羁绊等级评分
|
||||
"""
|
||||
score = 0.0
|
||||
|
||||
# 处理职业羁绊
|
||||
for job_level in team.synergy_levels.get('job', []):
|
||||
level = job_level['level']
|
||||
count = job_level['count']
|
||||
# 计算该羁绊的得分,根据等级和数量
|
||||
level_multiplier = self.config.level_multipliers.get(level, 1.0)
|
||||
# 额外激活的羁绊单位也计入评分
|
||||
extra_units = max(0, count - level)
|
||||
level_score = level * level_multiplier + extra_units * 0.5
|
||||
score += level_score
|
||||
|
||||
# 处理特质羁绊
|
||||
for race_level in team.synergy_levels.get('race', []):
|
||||
level = race_level['level']
|
||||
count = race_level['count']
|
||||
# 计算该羁绊的得分
|
||||
level_multiplier = self.config.level_multipliers.get(level, 1.0)
|
||||
extra_units = max(0, count - level)
|
||||
level_score = level * level_multiplier + extra_units * 0.5
|
||||
score += level_score
|
||||
|
||||
return score
|
||||
|
||||
def _score_synergy_count(self, team) -> float:
|
||||
"""
|
||||
评分羁绊种类数量
|
||||
|
||||
Args:
|
||||
team: TeamComposition实例
|
||||
|
||||
Returns:
|
||||
float: 羁绊种类数量评分
|
||||
"""
|
||||
# 计算激活的羁绊种类数量
|
||||
job_count = len(team.synergy_levels.get('job', []))
|
||||
race_count = len(team.synergy_levels.get('race', []))
|
||||
|
||||
# 根据羁绊种类数量评分
|
||||
# 羁绊越多,评分越高,但有一个合理的上限
|
||||
total_count = job_count + race_count
|
||||
# 使用对数函数避免羁绊数量过多时分数过高
|
||||
score = (1 + total_count) ** 0.8
|
||||
|
||||
return score
|
||||
|
||||
def _score_chess_cost(self, team) -> float:
|
||||
"""
|
||||
评分棋子费用分布
|
||||
|
||||
Args:
|
||||
team: TeamComposition实例
|
||||
|
||||
Returns:
|
||||
float: 棋子费用评分
|
||||
"""
|
||||
score = 0.0
|
||||
|
||||
# 按照棋子费用评分
|
||||
for chess in team.chess_list:
|
||||
cost = chess.get('price', '1')
|
||||
cost_multiplier = self.config.cost_multipliers.get(cost, 1.0)
|
||||
score += float(cost) * cost_multiplier
|
||||
|
||||
# 费用分布的平衡性评分
|
||||
# 根据阵容人口,期望的费用分布可能不同
|
||||
# 这里可以进一步完善
|
||||
|
||||
return score
|
||||
|
||||
def customize_scoring(self, **kwargs) -> None:
|
||||
"""
|
||||
自定义评分参数
|
||||
|
||||
Args:
|
||||
**kwargs: 评分参数,如synergy_level_weight, chess_cost_weight等
|
||||
"""
|
||||
for key, value in kwargs.items():
|
||||
if hasattr(self.config, key):
|
||||
setattr(self.config, key, value)
|
||||
logger.info(f"设置评分参数 {key} = {value}")
|
||||
else:
|
||||
logger.warning(f"未知的评分参数: {key}")
|
Loading…
x
Reference in New Issue
Block a user