完成阵容推荐器编写

This commit is contained in:
hxuanyu 2025-04-02 09:52:28 +08:00
parent b014895061
commit fd7392c6c9
9 changed files with 947 additions and 373 deletions

132
README.md
View File

@ -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] 阵容评分模块
- [ ] 接口模块
- [ ] 图形用户界面

View File

@ -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让我们一起将该项目做得更完善、易用、智能。祝各位在云顶之弈的旅程中诸神之巅

View File

@ -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__":

View File

@ -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

View File

@ -1,3 +1,6 @@
"""
阵容推荐模块
"""
"""
from .recommendation_engine import RecommendationEngine, TeamComposition
__all__ = ['RecommendationEngine', 'TeamComposition']

View 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
View 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()

View File

@ -1,3 +1,6 @@
"""
阵容评分模块
"""
"""
from .scoring_system import TeamScorer, ScoringConfig
__all__ = ['TeamScorer', 'ScoringConfig']

View 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}")