diff --git a/data/chess.json b/data/chess.json index f81119d..58e1fe8 100644 --- a/data/chess.json +++ b/data/chess.json @@ -2,7 +2,7 @@ "version": "15.7", "season": "2025.S14", "modeId": "1", - "time": "2025-04-01 14:31:08", + "time": "2025-04-02 00:32:23", "data": [ { "chessId": "10274", @@ -89,8 +89,8 @@ "skillName": "击膝者", "skillType": "主动", "skillImage": "https://game.gtimg.cn/images/lol/act/img/tft/champions/tft14_dariusspell.tft_set14.png", - "skillIntroduce": "回复238/288/362 (AP生命值)生命值,随后对1格内的敌人们造成130/195/293 (AD)物理伤害和持续6秒的20%护甲击碎。_x000D_\n【主理人】加成:技能距离提升至4格。每远一格,伤害降低10%。每命中一个敌人,回复65/77/99(生命值)生命值。", - "skillDetail": "回复238/288/362 (AP生命值)生命值,随后对1格内的敌人们造成130/195/293 (AD)物理伤害和持续6秒的20%护甲击碎。_x000D_\n【主理人】加成:技能距离提升至4格。每远一格,伤害降低10%。每命中一个敌人,回复65/77/99(生命值)生命值。", + "skillIntroduce": "回复238/288/362 (AP生命值)生命值,随后对1格内的敌人们造成130/195/293 (AD)物理伤害和持续6秒的20%护甲击碎。\r\n【主理人】加成:技能距离提升至4格。每远一格,伤害降低10%。每命中一个敌人,回复65/77/99(生命值)生命值。", + "skillDetail": "回复238/288/362 (AP生命值)生命值,随后对1格内的敌人们造成130/195/293 (AD)物理伤害和持续6秒的20%护甲击碎。\r\n【主理人】加成:技能距离提升至4格。每远一格,伤害降低10%。每命中一个敌人,回复65/77/99(生命值)生命值。", "life": "750", "magic": "80", "startMagic": "30", @@ -126,8 +126,8 @@ "skillName": "大展身手", "skillType": "主动", "skillImage": "https://game.gtimg.cn/images/lol/act/img/tft/champions/tft14_drmundo_r.tft_set14.png", - "skillIntroduce": "被动:额外获得30% 来自所有来源的最大生命值。(额外生命值:)_x000D_\n主动:施展身手,回复88/138/217 (生命值AP)生命值,然后对当前目标造成195/332/567 (生命值AD)物理伤害。", - "skillDetail": "被动:额外获得30% 来自所有来源的最大生命值。(额外生命值:)_x000D_\n主动:施展身手,回复88/138/217 (生命值AP)生命值,然后对当前目标造成195/332/567 (生命值AD)物理伤害。", + "skillIntroduce": "被动:额外获得30% 来自所有来源的最大生命值。(额外生命值:)\r\n主动:施展身手,回复88/138/217 (生命值AP)生命值,然后对当前目标造成195/332/567 (生命值AD)物理伤害。", + "skillDetail": "被动:额外获得30% 来自所有来源的最大生命值。(额外生命值:)\r\n主动:施展身手,回复88/138/217 (生命值AP)生命值,然后对当前目标造成195/332/567 (生命值AD)物理伤害。", "life": "650", "magic": "90", "startMagic": "30", @@ -348,8 +348,8 @@ "skillName": "扫街时间", "skillType": "主动", "skillImage": "https://game.gtimg.cn/images/lol/act/img/tft/champions/tft14_missfortunespell.tft_set14.png", - "skillIntroduce": "在2秒内持续向敌人最密集的位置发射8波子弹。被命中的敌人从每波攻击的第一颗子弹中受到33/50/269 (AD)物理伤害,从同一波的其它子弹中受到的伤害则降低25%。_x000D_\n【主理人】加成:发射的波数增加4。子弹会穿过敌人,造成50%伤害。", - "skillDetail": "在2秒内持续向敌人最密集的位置发射8波子弹。被命中的敌人从每波攻击的第一颗子弹中受到33/50/269 (AD)物理伤害,从同一波的其它子弹中受到的伤害则降低25%。_x000D_\n【主理人】加成:发射的波数增加4。子弹会穿过敌人,造成50%伤害。", + "skillIntroduce": "在2秒内持续向敌人最密集的位置发射8波子弹。被命中的敌人从每波攻击的第一颗子弹中受到33/50/269 (AD)物理伤害,从同一波的其它子弹中受到的伤害则降低25%。\r\n【主理人】加成:发射的波数增加4。子弹会穿过敌人,造成50%伤害。", + "skillDetail": "在2秒内持续向敌人最密集的位置发射8波子弹。被命中的敌人从每波攻击的第一颗子弹中受到33/50/269 (AD)物理伤害,从同一波的其它子弹中受到的伤害则降低25%。\r\n【主理人】加成:发射的波数增加4。子弹会穿过敌人,造成50%伤害。", "life": "800", "magic": "150", "startMagic": "50", @@ -459,8 +459,8 @@ "skillName": "终极统治", "skillType": "主动", "skillImage": "https://game.gtimg.cn/images/lol/act/img/tft/champions/tft14_renektonspell.tft_set14.png", - "skillIntroduce": "对2格内的敌人们造成228/337/4955 (ADAP)物理伤害和持续5秒的灼烧与重伤。获得400/550/30000最大生命值。_x000D_\n第一次施放:进入狂怒模式,每次攻击进行两次打击、造成116/174/1486 (AD)总物理伤害并且能够冲刺至新目标身边。_x000D_\n第二次施放:狂怒模式现在进行三次打击,造成177/267/2970 (AD)总物理伤害。无法再次施放。", - "skillDetail": "对2格内的敌人们造成228/337/4955 (ADAP)物理伤害和持续5秒的灼烧与重伤。获得400/550/30000最大生命值。_x000D_\n第一次施放:进入狂怒模式,每次攻击进行两次打击、造成116/174/1486 (AD)总物理伤害并且能够冲刺至新目标身边。_x000D_\n第二次施放:狂怒模式现在进行三次打击,造成177/267/2970 (AD)总物理伤害。无法再次施放。", + "skillIntroduce": "对2格内的敌人们造成228/337/4955 (ADAP)物理伤害和持续5秒的灼烧与重伤。获得400/550/30000最大生命值。\r\n第一次施放:进入狂怒模式,每次攻击进行两次打击、造成116/174/1486 (AD)总物理伤害并且能够冲刺至新目标身边。\r\n第二次施放:狂怒模式现在进行三次打击,造成177/267/2970 (AD)总物理伤害。无法再次施放。", + "skillDetail": "对2格内的敌人们造成228/337/4955 (ADAP)物理伤害和持续5秒的灼烧与重伤。获得400/550/30000最大生命值。\r\n第一次施放:进入狂怒模式,每次攻击进行两次打击、造成116/174/1486 (AD)总物理伤害并且能够冲刺至新目标身边。\r\n第二次施放:狂怒模式现在进行三次打击,造成177/267/2970 (AD)总物理伤害。无法再次施放。", "life": "1000", "magic": "150", "startMagic": "70", @@ -496,8 +496,8 @@ "skillName": "即兴发挥", "skillType": "主动", "skillImage": "https://game.gtimg.cn/images/lol/act/img/tft/champions/tft14_rengarspell.tft_set14.png", - "skillIntroduce": "回复160/180/210 (AP)生命值,并跃向2格内生命值最低的那个敌人,造成163/244/366 (AD)物理伤害。然后,快速连续击打2次,每次造成117/176/263 (AD)物理伤害。_x000D_\n每次施放会使跳跃距离提升1格。", - "skillDetail": "回复160/180/210 (AP)生命值,并跃向2格内生命值最低的那个敌人,造成163/244/366 (AD)物理伤害。然后,快速连续击打2次,每次造成117/176/263 (AD)物理伤害。_x000D_\n每次施放会使跳跃距离提升1格。", + "skillIntroduce": "回复160/180/210 (AP)生命值,并跃向2格内生命值最低的那个敌人,造成163/244/366 (AD)物理伤害。然后,快速连续击打2次,每次造成117/176/263 (AD)物理伤害。\r\n每次施放会使跳跃距离提升1格。", + "skillDetail": "回复160/180/210 (AP)生命值,并跃向2格内生命值最低的那个敌人,造成163/244/366 (AD)物理伤害。然后,快速连续击打2次,每次造成117/176/263 (AD)物理伤害。\r\n每次施放会使跳跃距离提升1格。", "life": "800", "magic": "60", "startMagic": "20", @@ -644,8 +644,8 @@ "skillName": "最高王牌", "skillType": "主动", "skillImage": "https://game.gtimg.cn/images/lol/act/img/tft/champions/tft14_twistedfatespell.tft_set14.png", - "skillIntroduce": "被动: 每次攻击获得2法术强度。_x000D_\n主动:向当前目标及下一个相距最近的目标投掷一张牌,造成150/225/340 (AP)魔法伤害。_x000D_\n【主理人】加成:向三个附近敌人投掷全部三种特殊卡牌。红色卡牌命中1格范围内的敌人们。蓝色卡牌造成其50%伤害的真实伤害。在少见情况下,扔出一张提供金币的黄色卡牌。", - "skillDetail": "被动: 每次攻击获得2法术强度。_x000D_\n主动:向当前目标及下一个相距最近的目标投掷一张牌,造成150/225/340 (AP)魔法伤害。_x000D_\n【主理人】加成:向三个附近敌人投掷全部三种特殊卡牌。红色卡牌命中1格范围内的敌人们。蓝色卡牌造成其50%伤害的真实伤害。在少见情况下,扔出一张提供金币的黄色卡牌。", + "skillIntroduce": "被动: 每次攻击获得2法术强度。\r\n主动:向当前目标及下一个相距最近的目标投掷一张牌,造成150/225/340 (AP)魔法伤害。\r\n【主理人】加成:向三个附近敌人投掷全部三种特殊卡牌。红色卡牌命中1格范围内的敌人们。蓝色卡牌造成其50%伤害的真实伤害。在少见情况下,扔出一张提供金币的黄色卡牌。", + "skillDetail": "被动: 每次攻击获得2法术强度。\r\n主动:向当前目标及下一个相距最近的目标投掷一张牌,造成150/225/340 (AP)魔法伤害。\r\n【主理人】加成:向三个附近敌人投掷全部三种特殊卡牌。红色卡牌命中1格范围内的敌人们。蓝色卡牌造成其50%伤害的真实伤害。在少见情况下,扔出一张提供金币的黄色卡牌。", "life": "600", "magic": "70", "startMagic": "10", @@ -718,8 +718,8 @@ "skillName": "赛博霸权", "skillType": "主动", "skillImage": "https://game.gtimg.cn/images/lol/act/img/tft/champions/tft14_veigar_ability.tft_set14.png", - "skillIntroduce": "对当前目标造成320/420/560 (AP)魔法伤害 并对邻格们的敌人造成125/170/240 (AP)魔法伤害。_x000D_\n如果【维迦】与目标同星级,造成25%真实伤害。如果【维迦】的星级更高,造成40%真实伤害。", - "skillDetail": "对当前目标造成320/420/560 (AP)魔法伤害 并对邻格们的敌人造成125/170/240 (AP)魔法伤害。_x000D_\n如果【维迦】与目标同星级,造成25%真实伤害。如果【维迦】的星级更高,造成40%真实伤害。", + "skillIntroduce": "对当前目标造成320/420/560 (AP)魔法伤害 并对邻格们的敌人造成125/170/240 (AP)魔法伤害。\r\n如果【维迦】与目标同星级,造成25%真实伤害。如果【维迦】的星级更高,造成40%真实伤害。", + "skillDetail": "对当前目标造成320/420/560 (AP)魔法伤害 并对邻格们的敌人造成125/170/240 (AP)魔法伤害。\r\n如果【维迦】与目标同星级,造成25%真实伤害。如果【维迦】的星级更高,造成40%真实伤害。", "life": "550", "magic": "40", "startMagic": "", @@ -755,8 +755,8 @@ "skillName": "报复", "skillType": "主动", "skillImage": "https://game.gtimg.cn/images/lol/act/img/tft/champions/tft14_vexr.tft_set14.png", - "skillIntroduce": "被动:获得15%全能吸血。来自【报复】的150%过度治疗会被转化为对它主要目标的额外真实伤害。_x000D_\n主动:派出一个阴影前往目标,对它途经的所有敌人造成100/150/600 (AP)魔法伤害。阴影随后会爆炸,对目标造成190/285/1100 (AP)魔法伤害并对1格内的敌人们造成100/150/600 (AP)魔法伤害。", - "skillDetail": "被动:获得15%全能吸血。来自【报复】的150%过度治疗会被转化为对它主要目标的额外真实伤害。_x000D_\n主动:派出一个阴影前往目标,对它途经的所有敌人造成100/150/600 (AP)魔法伤害。阴影随后会爆炸,对目标造成190/285/1100 (AP)魔法伤害并对1格内的敌人们造成100/150/600 (AP)魔法伤害。", + "skillIntroduce": "被动:获得15%全能吸血。来自【报复】的150%过度治疗会被转化为对它主要目标的额外真实伤害。\r\n主动:派出一个阴影前往目标,对它途经的所有敌人造成100/150/600 (AP)魔法伤害。阴影随后会爆炸,对目标造成190/285/1100 (AP)魔法伤害并对1格内的敌人们造成100/150/600 (AP)魔法伤害。", + "skillDetail": "被动:获得15%全能吸血。来自【报复】的150%过度治疗会被转化为对它主要目标的额外真实伤害。\r\n主动:派出一个阴影前往目标,对它途经的所有敌人造成100/150/600 (AP)魔法伤害。阴影随后会爆炸,对目标造成190/285/1100 (AP)魔法伤害并对1格内的敌人们造成100/150/600 (AP)魔法伤害。", "life": "800", "magic": "30", "startMagic": "", @@ -829,8 +829,8 @@ "skillName": "超线程狂潮", "skillType": "主动", "skillImage": "https://game.gtimg.cn/images/lol/act/img/tft/champions/tft14_zerispell.tft_set14.png", - "skillIntroduce": "快速冲刺到一个附近的位置,留下一个不可被选取的残影。残影会模仿【泽丽】进行攻击 (AD攻击速度),但造成40/40/200%伤害。_x000D_\n残影持续5/5/10 (AP)秒,并会在【泽丽】阵亡时消失。", - "skillDetail": "快速冲刺到一个附近的位置,留下一个不可被选取的残影。残影会模仿【泽丽】进行攻击 (AD攻击速度),但造成40/40/200%伤害。_x000D_\n残影持续5/5/10 (AP)秒,并会在【泽丽】阵亡时消失。", + "skillIntroduce": "快速冲刺到一个附近的位置,留下一个不可被选取的残影。残影会模仿【泽丽】进行攻击 (AD攻击速度),但造成40/40/200%伤害。\r\n残影持续5/5/10 (AP)秒,并会在【泽丽】阵亡时消失。", + "skillDetail": "快速冲刺到一个附近的位置,留下一个不可被选取的残影。残影会模仿【泽丽】进行攻击 (AD攻击速度),但造成40/40/200%伤害。\r\n残影持续5/5/10 (AP)秒,并会在【泽丽】阵亡时消失。", "life": "800", "magic": "40", "startMagic": "", @@ -977,8 +977,8 @@ "skillName": "烈火燎原", "skillType": "主动", "skillImage": "https://game.gtimg.cn/images/lol/act/img/tft/champions/tft14_shyvanaburnout.tft_set14.png", - "skillIntroduce": "第一次施放:在当前战斗结束前的每一秒,回复13/21/33 (AP生命值)生命值并对邻格的敌人造成25/40/63 (AP生命值)魔法伤害。_x000D_\n获得200/250/300 (AP)最大生命值和10%伤害增幅。", - "skillDetail": "第一次施放:在当前战斗结束前的每一秒,回复13/21/33 (AP生命值)生命值并对邻格的敌人造成25/40/63 (AP生命值)魔法伤害。_x000D_\n获得200/250/300 (AP)最大生命值和10%伤害增幅。", + "skillIntroduce": "第一次施放:在当前战斗结束前的每一秒,回复13/21/33 (AP生命值)生命值并对邻格的敌人造成25/40/63 (AP生命值)魔法伤害。\r\n获得200/250/300 (AP)最大生命值和10%伤害增幅。", + "skillDetail": "第一次施放:在当前战斗结束前的每一秒,回复13/21/33 (AP生命值)生命值并对邻格的敌人造成25/40/63 (AP生命值)魔法伤害。\r\n获得200/250/300 (AP)最大生命值和10%伤害增幅。", "life": "700", "magic": "115", "startMagic": "40", @@ -1051,8 +1051,8 @@ "skillName": "智能悠米", "skillType": "主动", "skillImage": "https://game.gtimg.cn/images/lol/act/img/tft/champions/tft14_yuumiw.tft_set14.png", - "skillIntroduce": "被动:每秒获得3 (超频属性)法力值。_x000D_\n主动:对当前目标发射一个魔法弹,这个魔法弹会弹射至相距最近的那个敌人。对当前目标造成95/145/220 (AP)魔法伤害并对另一个造成55/85/125 (AP)魔法伤害,并将二者标记。如果其已被标记,则转而造成175%魔法伤害。", - "skillDetail": "被动:每秒获得3 (超频属性)法力值。_x000D_\n主动:对当前目标发射一个魔法弹,这个魔法弹会弹射至相距最近的那个敌人。对当前目标造成95/145/220 (AP)魔法伤害并对另一个造成55/85/125 (AP)魔法伤害,并将二者标记。如果其已被标记,则转而造成175%魔法伤害。", + "skillIntroduce": "被动:每秒获得3 (超频属性)法力值。\r\n主动:对当前目标发射一个魔法弹,这个魔法弹会弹射至相距最近的那个敌人。对当前目标造成95/145/220 (AP)魔法伤害并对另一个造成55/85/125 (AP)魔法伤害,并将二者标记。如果其已被标记,则转而造成175%魔法伤害。", + "skillDetail": "被动:每秒获得3 (超频属性)法力值。\r\n主动:对当前目标发射一个魔法弹,这个魔法弹会弹射至相距最近的那个敌人。对当前目标造成95/145/220 (AP)魔法伤害并对另一个造成55/85/125 (AP)魔法伤害,并将二者标记。如果其已被标记,则转而造成175%魔法伤害。", "life": "650", "magic": "30", "startMagic": "", @@ -1236,8 +1236,8 @@ "skillName": "领头狗指令", "skillType": "主动", "skillImage": "https://game.gtimg.cn/images/lol/act/img/tft/champions/icons_tft14_naafiri_e.tft_set14.png", - "skillIntroduce": "被动:获得15% (AP)全能吸血。_x000D_\n主动:对当前目标造成138/207/310 (AD)物理伤害。然后,对当前目标召唤3个造成34/51/82 (AD)物理伤害的狗群伙伴,以及2 (超频属性)个造成86/129/205 (AD)物理伤害的领头狗。", - "skillDetail": "被动:获得15% (AP)全能吸血。_x000D_\n主动:对当前目标造成138/207/310 (AD)物理伤害。然后,对当前目标召唤3个造成34/51/82 (AD)物理伤害的狗群伙伴,以及2 (超频属性)个造成86/129/205 (AD)物理伤害的领头狗。", + "skillIntroduce": "被动:获得15% (AP)全能吸血。\r\n主动:对当前目标造成138/207/310 (AD)物理伤害。然后,对当前目标召唤3个造成34/51/82 (AD)物理伤害的狗群伙伴,以及2 (超频属性)个造成86/129/205 (AD)物理伤害的领头狗。", + "skillDetail": "被动:获得15% (AP)全能吸血。\r\n主动:对当前目标造成138/207/310 (AD)物理伤害。然后,对当前目标召唤3个造成34/51/82 (AD)物理伤害的狗群伙伴,以及2 (超频属性)个造成86/129/205 (AD)物理伤害的领头狗。", "life": "700", "magic": "50", "startMagic": "", @@ -1421,8 +1421,8 @@ "skillName": "游戏结束!", "skillType": "主动", "skillImage": "https://game.gtimg.cn/images/lol/act/img/tft/champions/tft_set14_spell_cyberbosskobuko.tft_set14.png", - "skillIntroduce": "被动:攻击造成90/135/314 (AP)额外魔法伤害。在35/35/90%生命值时,获得在8秒内持续衰减的700/1260/12960 (生命值)护盾值、100%攻击速度、20/20/100%全能吸血、以及100法力值。_x000D_\n主动:短暂获得60/60/99%伤害减免,随后抬起一名附近敌人,并击飞2格内的所有敌人,持续1.5秒。然后,狠狠砸落,对目标造成200/300/13370 (AP)魔法伤害,并对命中的所有敌人造成该目标0.15/0.25/2 (AP)最大生命值的魔法伤害。", - "skillDetail": "被动:攻击造成90/135/314 (AP)额外魔法伤害。在35/35/90%生命值时,获得在8秒内持续衰减的700/1260/12960 (生命值)护盾值、100%攻击速度、20/20/100%全能吸血、以及100法力值。_x000D_\n主动:短暂获得60/60/99%伤害减免,随后抬起一名附近敌人,并击飞2格内的所有敌人,持续1.5秒。然后,狠狠砸落,对目标造成200/300/13370 (AP)魔法伤害,并对命中的所有敌人造成该目标0.15/0.25/2 (AP)最大生命值的魔法伤害。", + "skillIntroduce": "被动:攻击造成90/135/314 (AP)额外魔法伤害。在35/35/90%生命值时,获得在8秒内持续衰减的700/1260/12960 (生命值)护盾值、100%攻击速度、20/20/100%全能吸血、以及100法力值。\r\n主动:短暂获得60/60/99%伤害减免,随后抬起一名附近敌人,并击飞2格内的所有敌人,持续1.5秒。然后,狠狠砸落,对目标造成200/300/13370 (AP)魔法伤害,并对命中的所有敌人造成该目标0.15/0.25/2 (AP)最大生命值的魔法伤害。", + "skillDetail": "被动:攻击造成90/135/314 (AP)额外魔法伤害。在35/35/90%生命值时,获得在8秒内持续衰减的700/1260/12960 (生命值)护盾值、100%攻击速度、20/20/100%全能吸血、以及100法力值。\r\n主动:短暂获得60/60/99%伤害减免,随后抬起一名附近敌人,并击飞2格内的所有敌人,持续1.5秒。然后,狠狠砸落,对目标造成200/300/13370 (AP)魔法伤害,并对命中的所有敌人造成该目标0.15/0.25/2 (AP)最大生命值的魔法伤害。", "life": "1000", "magic": "220", "startMagic": "100", @@ -1458,8 +1458,8 @@ "skillName": "蓝屏", "skillType": "主动", "skillImage": "https://game.gtimg.cn/images/lol/act/img/tft/champions/tft14_sejuani_w.tft_set14.png", - "skillIntroduce": "被动:从所有来源获得的护甲和魔抗额外提升30%。_x000D_\n主动:向当前目标投掷电磁脉冲炸弹,炸弹会在2秒后爆炸。它对3格内的敌人造成120/180/1800 (AP)魔法伤害和1.75/2/8秒晕眩。", - "skillDetail": "被动:从所有来源获得的护甲和魔抗额外提升30%。_x000D_\n主动:向当前目标投掷电磁脉冲炸弹,炸弹会在2秒后爆炸。它对3格内的敌人造成120/180/1800 (AP)魔法伤害和1.75/2/8秒晕眩。", + "skillIntroduce": "被动:从所有来源获得的护甲和魔抗额外提升30%。\r\n主动:向当前目标投掷电磁脉冲炸弹,炸弹会在2秒后爆炸。它对3格内的敌人造成120/180/1800 (AP)魔法伤害和1.75/2/8秒晕眩。", + "skillDetail": "被动:从所有来源获得的护甲和魔抗额外提升30%。\r\n主动:向当前目标投掷电磁脉冲炸弹,炸弹会在2秒后爆炸。它对3格内的敌人造成120/180/1800 (AP)魔法伤害和1.75/2/8秒晕眩。", "life": "1000", "magic": "140", "startMagic": "50", @@ -1606,8 +1606,8 @@ "skillName": "超越死亡的恐惧", "skillType": "主动", "skillImage": "https://game.gtimg.cn/images/lol/act/img/tft/champions/tft14_urgot_e.tft_set14.png", - "skillIntroduce": "在5/5/60秒期间里,获得125/125/500%攻击速度并锁定5格内百分比生命值最低的那个敌人。普攻还会发射3枚火箭,每枚造成68/102/1516 (AD)总物理伤害。_x000D_\n每次施放,如果一个被锁定的敌人生命值首次降至15/15/95%以下,则将其处决、拖向自己,并将其碾碎成零件。", - "skillDetail": "在5/5/60秒期间里,获得125/125/500%攻击速度并锁定5格内百分比生命值最低的那个敌人。普攻还会发射3枚火箭,每枚造成68/102/1516 (AD)总物理伤害。_x000D_\n每次施放,如果一个被锁定的敌人生命值首次降至15/15/95%以下,则将其处决、拖向自己,并将其碾碎成零件。", + "skillIntroduce": "在5/5/60秒期间里,获得125/125/500%攻击速度并锁定5格内百分比生命值最低的那个敌人。普攻还会发射3枚火箭,每枚造成68/102/1516 (AD)总物理伤害。\r\n每次施放,如果一个被锁定的敌人生命值首次降至15/15/95%以下,则将其处决、拖向自己,并将其碾碎成零件。", + "skillDetail": "在5/5/60秒期间里,获得125/125/500%攻击速度并锁定5格内百分比生命值最低的那个敌人。普攻还会发射3枚火箭,每枚造成68/102/1516 (AD)总物理伤害。\r\n每次施放,如果一个被锁定的敌人生命值首次降至15/15/95%以下,则将其处决、拖向自己,并将其碾碎成零件。", "life": "850", "magic": "50", "startMagic": "", @@ -1680,8 +1680,8 @@ "skillName": "灵纱穿透", "skillType": "主动", "skillImage": "https://game.gtimg.cn/images/lol/act/img/tft/champions/tft14_aurorae.tft_set14.png", - "skillIntroduce": "首次施放时,与你的备战席上最左边的弈子交换位置然后继续战斗。该弈子获得50%/100%/1000% (AP)攻击速度。如果被交换位置的弈子阵亡,则回到棋盘。_x000D_\n后续施放时,传送到敌人最多的一条直线进行打击,对命中的每名敌人造成50/125/3333 (AP)伤害,外加由所有被命中的敌人均摊的575/1000/9999 (AP)伤害。_x000D_\n【阿萝拉】无法和另一个【阿萝拉】交换位置。", - "skillDetail": "首次施放时,与你的备战席上最左边的弈子交换位置然后继续战斗。该弈子获得50%/100%/1000% (AP)攻击速度。如果被交换位置的弈子阵亡,则回到棋盘。_x000D_\n后续施放时,传送到敌人最多的一条直线进行打击,对命中的每名敌人造成50/125/3333 (AP)伤害,外加由所有被命中的敌人均摊的575/1000/9999 (AP)伤害。_x000D_\n【阿萝拉】无法和另一个【阿萝拉】交换位置。", + "skillIntroduce": "首次施放时,与你的备战席上最左边的弈子交换位置然后继续战斗。该弈子获得50%/100%/1000% (AP)攻击速度。如果被交换位置的弈子阵亡,则回到棋盘。\r\n后续施放时,传送到敌人最多的一条直线进行打击,对命中的每名敌人造成50/125/3333 (AP)伤害,外加由所有被命中的敌人均摊的575/1000/9999 (AP)伤害。\r\n【阿萝拉】无法和另一个【阿萝拉】交换位置。", + "skillDetail": "首次施放时,与你的备战席上最左边的弈子交换位置然后继续战斗。该弈子获得50%/100%/1000% (AP)攻击速度。如果被交换位置的弈子阵亡,则回到棋盘。\r\n后续施放时,传送到敌人最多的一条直线进行打击,对命中的每名敌人造成50/125/3333 (AP)伤害,外加由所有被命中的敌人均摊的575/1000/9999 (AP)伤害。\r\n【阿萝拉】无法和另一个【阿萝拉】交换位置。", "life": "800", "magic": "80", "startMagic": "20", @@ -1754,8 +1754,8 @@ "skillName": "日炎耀斑", "skillType": "主动", "skillImage": "https://game.gtimg.cn/images/lol/act/img/tft/champions/tft14_leonae.tft_set14.png", - "skillIntroduce": "获得持续4秒的55%/60%/90% (AP)伤害减免。随后,对2格内敌人最密集的位置造成100/150/2000 (AP)魔法伤害,并对中心的那个敌人造成2秒晕眩。_x000D_\n受到此技能伤害的敌人在承受友军普攻时会受到8/13/90 (魔抗)额外魔法伤害,持续4/4/10秒。", - "skillDetail": "获得持续4秒的55%/60%/90% (AP)伤害减免。随后,对2格内敌人最密集的位置造成100/150/2000 (AP)魔法伤害,并对中心的那个敌人造成2秒晕眩。_x000D_\n受到此技能伤害的敌人在承受友军普攻时会受到8/13/90 (魔抗)额外魔法伤害,持续4/4/10秒。", + "skillIntroduce": "获得持续4秒的55%/60%/90% (AP)伤害减免。随后,对2格内敌人最密集的位置造成100/150/2000 (AP)魔法伤害,并对中心的那个敌人造成2秒晕眩。\r\n受到此技能伤害的敌人在承受友军普攻时会受到8/13/90 (魔抗)额外魔法伤害,持续4/4/10秒。", + "skillDetail": "获得持续4秒的55%/60%/90% (AP)伤害减免。随后,对2格内敌人最密集的位置造成100/150/2000 (AP)魔法伤害,并对中心的那个敌人造成2秒晕眩。\r\n受到此技能伤害的敌人在承受友军普攻时会受到8/13/90 (魔抗)额外魔法伤害,持续4/4/10秒。", "life": "1100", "magic": "110", "startMagic": "50", @@ -1865,8 +1865,8 @@ "skillName": "鎏金耐久", "skillType": "主动", "skillImage": "https://game.gtimg.cn/images/lol/act/img/tft/champions/tft14_alistar_r.tft_set14.png", - "skillIntroduce": "被动:受到的所有伤害降低12/18/25 (AP)。_x000D_\n主动:对当前目标造成200/300/450 (AP)魔法伤害和2秒晕眩。", - "skillDetail": "被动:受到的所有伤害降低12/18/25 (AP)。_x000D_\n主动:对当前目标造成200/300/450 (AP)魔法伤害和2秒晕眩。", + "skillIntroduce": "被动:受到的所有伤害降低12/18/25 (AP)。\r\n主动:对当前目标造成200/300/450 (AP)魔法伤害和2秒晕眩。", + "skillDetail": "被动:受到的所有伤害降低12/18/25 (AP)。\r\n主动:对当前目标造成200/300/450 (AP)魔法伤害和2秒晕眩。", "life": "650", "magic": "100", "startMagic": "30", @@ -1976,8 +1976,8 @@ "skillName": "迈达斯霰弹", "skillType": "主动", "skillImage": "https://game.gtimg.cn/images/lol/act/img/tft/champions/tft14_gravese.tft_set14.png", - "skillIntroduce": "被动:普攻会在一个锥形范围内发射5枚飞弹,每枚飞弹造成18/26/39 (AD)物理伤害。格雷福斯每2次普攻进行一次技能施放。_x000D_\n主动:冲刺至当前目标旁边并快速发射2颗强化子弹,这些强化子弹的每枚飞弹造成26/39/67 (ADAP)物理伤害。", - "skillDetail": "被动:普攻会在一个锥形范围内发射5枚飞弹,每枚飞弹造成18/26/39 (AD)物理伤害。格雷福斯每2次普攻进行一次技能施放。_x000D_\n主动:冲刺至当前目标旁边并快速发射2颗强化子弹,这些强化子弹的每枚飞弹造成26/39/67 (ADAP)物理伤害。", + "skillIntroduce": "被动:普攻会在一个锥形范围内发射5枚飞弹,每枚飞弹造成18/26/39 (AD)物理伤害。格雷福斯每2次普攻进行一次技能施放。\r\n主动:冲刺至当前目标旁边并快速发射2颗强化子弹,这些强化子弹的每枚飞弹造成26/39/67 (ADAP)物理伤害。", + "skillDetail": "被动:普攻会在一个锥形范围内发射5枚飞弹,每枚飞弹造成18/26/39 (AD)物理伤害。格雷福斯每2次普攻进行一次技能施放。\r\n主动:冲刺至当前目标旁边并快速发射2颗强化子弹,这些强化子弹的每枚飞弹造成26/39/67 (ADAP)物理伤害。", "life": "800", "magic": "2", "startMagic": "", @@ -2013,8 +2013,8 @@ "skillName": "金发少女", "skillType": "主动", "skillImage": "https://game.gtimg.cn/images/lol/act/img/tft/champions/tft14_annie_r1.tft_set14.png", - "skillIntroduce": "向当前目标投掷一个火球,造成210/315/1200 (AP)魔法伤害,以及3 (超频属性)个小型火球在目标和2名附近敌人之间均摊,每个小型火球造成30/45/240 (AP)魔法伤害。_x000D_\n每施放4次,转而在当前目标身旁召唤提伯斯 (AP),对1格内的敌人们造成270/405/1215 (AP)魔法伤害。", - "skillDetail": "向当前目标投掷一个火球,造成210/315/1200 (AP)魔法伤害,以及3 (超频属性)个小型火球在目标和2名附近敌人之间均摊,每个小型火球造成30/45/240 (AP)魔法伤害。_x000D_\n每施放4次,转而在当前目标身旁召唤提伯斯 (AP),对1格内的敌人们造成270/405/1215 (AP)魔法伤害。", + "skillIntroduce": "向当前目标投掷一个火球,造成210/315/1200 (AP)魔法伤害,以及3 (超频属性)个小型火球在目标和2名附近敌人之间均摊,每个小型火球造成30/45/240 (AP)魔法伤害。_\r\n每施放4次,转而在当前目标身旁召唤提伯斯 (AP),对1格内的敌人们造成270/405/1215 (AP)魔法伤害。", + "skillDetail": "向当前目标投掷一个火球,造成210/315/1200 (AP)魔法伤害,以及3 (超频属性)个小型火球在目标和2名附近敌人之间均摊,每个小型火球造成30/45/240 (AP)魔法伤害。_\r\n每施放4次,转而在当前目标身旁召唤提伯斯 (AP),对1格内的敌人们造成270/405/1215 (AP)魔法伤害。", "life": "850", "magic": "40", "startMagic": "", @@ -2198,8 +2198,8 @@ "skillName": "自复制恶意软件", "skillType": "主动", "skillImage": "https://game.gtimg.cn/images/lol/act/img/tft/champions/tft14_zacpassive.tft_set14.png", - "skillIntroduce": "被动:在10%生命值时,分裂成两个拥有50/50/100%生命值的复制体。_x000D_\r\n主动:在附近敌人上弹跳3次。每次弹跳造成183/297/2377 (AP生命值)魔法伤害、1秒晕眩并回复【扎克】80/100/2000 (AP)生命值。", - "skillDetail": "被动:在10%生命值时,分裂成两个拥有50/50/100%生命值的复制体。_x000D_\r\n主动:在附近敌人上弹跳3次。每次弹跳造成183/297/2377 (AP生命值)魔法伤害、1秒晕眩并回复【扎克】80/100/2000 (AP)生命值。", + "skillIntroduce": "被动:在10%生命值时,分裂成两个拥有50/50/100%生命值的复制体。\r\n主动:在附近敌人上弹跳3次。每次弹跳造成183/297/2377 (AP生命值)魔法伤害、1秒晕眩并回复【扎克】80/100/2000 (AP)生命值。", + "skillDetail": "被动:在10%生命值时,分裂成两个拥有50/50/100%生命值的复制体。\r\n主动:在附近敌人上弹跳3次。每次弹跳造成183/297/2377 (AP生命值)魔法伤害、1秒晕眩并回复【扎克】80/100/2000 (AP)生命值。", "life": "900", "magic": "80", "startMagic": "30", @@ -2346,8 +2346,8 @@ "skillName": "清辉夜凝", "skillType": "主动", "skillImage": "https://game.gtimg.cn/images/lol/act/img/tft/champions/tft14_apheliosr.tft_set14.png", - "skillIntroduce": "朝敌人最密集的位置发射一道月华震波,在抵达时对2格内的敌人们造成159/238/951 (AD AP)物理伤害。_x000D_\n装备4个飞轮刃,并且月华震波每命中一个敌人就会提供1个额外的飞轮刃,持续6次攻击并造成4.41/6.62/70.88 (AD)x已有飞轮刃数量的额外物理伤害。", - "skillDetail": "朝敌人最密集的位置发射一道月华震波,在抵达时对2格内的敌人们造成159/238/951 (AD AP)物理伤害。_x000D_\n装备4个飞轮刃,并且月华震波每命中一个敌人就会提供1个额外的飞轮刃,持续6次攻击并造成4.41/6.62/70.88 (AD)x已有飞轮刃数量的额外物理伤害。", + "skillIntroduce": "朝敌人最密集的位置发射一道月华震波,在抵达时对2格内的敌人们造成159/238/951 (AD AP)物理伤害。\r\n装备4个飞轮刃,并且月华震波每命中一个敌人就会提供1个额外的飞轮刃,持续6次攻击并造成4.41/6.62/70.88 (AD)x已有飞轮刃数量的额外物理伤害。", + "skillDetail": "朝敌人最密集的位置发射一道月华震波,在抵达时对2格内的敌人们造成159/238/951 (AD AP)物理伤害。\r\n装备4个飞轮刃,并且月华震波每命中一个敌人就会提供1个额外的飞轮刃,持续6次攻击并造成4.41/6.62/70.88 (AD)x已有飞轮刃数量的额外物理伤害。", "life": "800", "magic": "60", "startMagic": "", diff --git a/src/recommendation/recommendation_engine.py b/src/recommendation/recommendation_engine.py index 2db0381..35ba6ff 100644 --- a/src/recommendation/recommendation_engine.py +++ b/src/recommendation/recommendation_engine.py @@ -181,6 +181,8 @@ class RecommendationEngine: # 2. 获取羁绊所需的棋子集合 synergy_chess_sets = [] + target_synergy_levels = {} + 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']) @@ -221,26 +223,116 @@ class RecommendationEngine: 'target_level': target_level, 'weight': weight # 添加权重信息 }) + + # 记录目标羁绊等级 + target_synergy_levels[synergy_id] = target_level - # 3. 根据已选棋子和羁绊要求生成候选阵容 - candidate_teams = self._generate_candidate_teams(base_team, synergy_chess_sets, population) + # 3. 使用多种方法生成候选阵容 + logger.info("开始生成候选阵容...") + candidate_teams = [] - # 如果没有生成候选阵容,则尝试填充最佳棋子 + # 3.1 首先使用传统方法生成阵容 + traditional_teams = self._generate_candidate_teams(base_team, synergy_chess_sets, population) + if traditional_teams: + logger.info(f"传统方法生成了 {len(traditional_teams)} 个候选阵容") + candidate_teams.extend(traditional_teams) + else: + logger.info("传统方法未能生成任何阵容") + + # 3.2 使用贪婪算法生成阵容 + greedy_teams = self._generate_teams_greedy(base_team, population, target_synergy_levels) + if greedy_teams: + logger.info(f"贪婪算法生成了 {len(greedy_teams)} 个候选阵容") + candidate_teams.extend(greedy_teams) + + # 3.3 使用穷举+剪枝算法生成阵容 + # 只在要求羁绊数不多时使用,避免搜索空间过大 + if len(target_synergy_levels) <= 4: + exhaustive_teams = self._generate_teams_exhaustive(base_team, population, target_synergy_levels, max_teams=20) + if exhaustive_teams: + logger.info(f"穷举+剪枝算法生成了 {len(exhaustive_teams)} 个候选阵容") + candidate_teams.extend(exhaustive_teams) + + # 3.4 如果仍然没有候选阵容,使用纯贪婪方法填充 if not candidate_teams: + logger.info("所有方法均未生成阵容,尝试使用贪婪填充") remaining_slots = population - base_team.size - logger.info(f"未找到满足所有羁绊要求的阵容,尝试填充最佳棋子 (剩余槽位: {remaining_slots})") - candidate_teams = self._fill_team_with_best_chess(base_team, remaining_slots) + candidate_teams = self._fill_team_with_best_chess(base_team, remaining_slots, multiple_variants=True) + + # 移除重复阵容 + logger.info(f"总共生成了 {len(candidate_teams)} 个候选阵容,开始去重") + unique_teams = [] + seen_chess_sets = set() - # 4. 计算每个阵容的评分并排序 for team in candidate_teams: + chess_names = frozenset(chess.get('displayName', '') for chess in team.chess_list) + if chess_names not in seen_chess_sets: + seen_chess_sets.add(chess_names) + unique_teams.append(team) + + candidate_teams = unique_teams + logger.info(f"去重后剩余 {len(candidate_teams)} 个候选阵容") + + # 限制评分计算的阵容数量,避免性能问题 + max_teams_to_evaluate = min(2000, len(candidate_teams)) + teams_to_evaluate = candidate_teams[:max_teams_to_evaluate] + + # 4. 计算每个阵容的羁绊和评分 + logger.info(f"开始评分 {len(teams_to_evaluate)} 个候选阵容") + + # 计算羁绊和评分 + for team in teams_to_evaluate: team.calculate_synergies(self.api) team.score = self.scorer.score_team(team) # 按分数从高到低排序 - candidate_teams.sort(key=lambda t: t.score, reverse=True) + teams_to_evaluate.sort(key=lambda t: t.score, reverse=True) - # 返回分数最高的几个阵容 - return candidate_teams[:max_results] + # 返回分数最高的几个阵容,并确保它们的差异性 + top_results = [] + seen_synergy_patterns = set() + + # 先添加评分最高的阵容 + if teams_to_evaluate: + top_team = teams_to_evaluate[0] + top_results.append(top_team) + synergy_pattern = frozenset((synergy_id, count) for synergy_id, count in top_team.synergy_counts.items() if count >= 1) + seen_synergy_patterns.add(synergy_pattern) + teams_to_evaluate.pop(0) + + # 然后添加有足够差异性的其他高分阵容 + for team in teams_to_evaluate: + if len(top_results) >= max_results: + break + + # 计算羁绊模式,用于判断阵容差异性 + synergy_pattern = frozenset((synergy_id, count) for synergy_id, count in team.synergy_counts.items() if count >= 1) + + # 如果这个羁绊模式与已有结果差异足够大,则添加 + is_different = True + for existing_pattern in seen_synergy_patterns: + # 计算交集大小 + intersection = len(set(synergy_pattern).intersection(existing_pattern)) + union = len(set(synergy_pattern).union(existing_pattern)) + + # Jaccard相似度 + if union > 0: + similarity = intersection / union + if similarity > 0.7: # 70%相似度作为阈值 + is_different = False + break + + if is_different: + top_results.append(team) + seen_synergy_patterns.add(synergy_pattern) + + # 如果找不到足够多有差异的阵容,返回评分最高的几个 + if len(top_results) < max_results and teams_to_evaluate: + remaining = max_results - len(top_results) + top_results.extend(teams_to_evaluate[:remaining]) + + logger.info(f"最终推荐了 {len(top_results)} 个阵容") + return top_results def _generate_candidate_teams( self, @@ -296,10 +388,13 @@ class RecommendationEngine: available_chess = [chess for chess in chess_list if chess.get('displayName') not in base_chess_names] - # 如果可用棋子不足以满足羁绊要求,则返回空列表 + # 如果可用棋子不足以满足羁绊要求 if len(available_chess) < needed_count: - logger.warning(f"羁绊 {synergy_set['synergy'].get('name', '')} 可用棋子不足,无法满足等级 {target_level} 的要求") - return [] + # 不直接返回空列表,而是将这个羁绊标记为尽可能满足 + logger.warning(f"羁绊 {synergy_set['synergy'].get('name', '')} 可用棋子不足,尝试最大程度满足") + if not available_chess: + continue + needed_count = len(available_chess) # 所有可能的棋子组合 chess_combinations = list(itertools.combinations(available_chess, needed_count)) @@ -314,17 +409,26 @@ class RecommendationEngine: # 按权重从高到低排序 weighted_combinations.sort(key=lambda x: x[1], reverse=True) - # 选择权重较高的前几个组合 - top_combinations = [combo for combo, _ in weighted_combinations[:min(5, len(weighted_combinations))]] + # 选择权重较高的前N个组合(增加组合数量) + max_combinations = min(20, len(weighted_combinations)) + top_combinations = [combo for combo, _ in weighted_combinations[:max_combinations]] min_chess_options.append(top_combinations) - # 如果某个羁绊无法满足,则返回空列表 - if not all(min_chess_options): - return [] + # 如果没有任何羁绊选项,则直接尝试填充最佳棋子 + if not min_chess_options: + remaining_slots = population - base_team.size + return self._fill_team_with_best_chess(base_team, remaining_slots) # 2. 尝试组合不同羁绊的棋子,生成可行阵容 # 为每个羁绊选择一种棋子组合方式 + max_teams_to_generate = 1000 # 限制生成的候选阵容数量,避免组合爆炸 + teams_generated = 0 + for chess_selection in itertools.product(*min_chess_options): + if teams_generated >= max_teams_to_generate: + logger.info(f"已生成{teams_generated}个候选阵容,达到上限") + break + # 创建新的候选阵容 candidate = TeamComposition() # 添加基础阵容的棋子 @@ -344,17 +448,78 @@ class RecommendationEngine: # 如果有剩余人口,填充最佳棋子 if candidate.size < population: remaining_slots = population - candidate.size - filled_candidates = self._fill_team_with_best_chess(candidate, remaining_slots) + filled_candidates = self._fill_team_with_best_chess(candidate, remaining_slots, multiple_variants=True) candidate_teams.extend(filled_candidates) + teams_generated += len(filled_candidates) else: candidate_teams.append(candidate) + teams_generated += 1 + # 如果没有生成任何候选阵容,则尝试放松某些羁绊的要求 + if not candidate_teams and min_chess_options: + logger.info("尝试放松羁绊要求,生成更多候选阵容") + # 尝试仅满足权重最高的部分羁绊 + priority_synergies = min_chess_options[:max(1, len(min_chess_options) // 2)] + + for chess_selection in itertools.product(*priority_synergies): + if teams_generated >= max_teams_to_generate: + break + + candidate = TeamComposition() + for chess in base_team.chess_list: + candidate.add_chess(chess) + + all_selected_chess = set() + for combo in chess_selection: + for chess in combo: + if chess.get('displayName') not in all_selected_chess: + candidate.add_chess(chess) + all_selected_chess.add(chess.get('displayName')) + + if candidate.size <= population: + if candidate.size < population: + remaining_slots = population - candidate.size + filled_candidates = self._fill_team_with_best_chess(candidate, remaining_slots, multiple_variants=True) + candidate_teams.extend(filled_candidates) + teams_generated += len(filled_candidates) + else: + candidate_teams.append(candidate) + teams_generated += 1 + + # 如果仍然没有生成候选阵容,尝试只满足单个最高权重的羁绊 + if not candidate_teams and min_chess_options: + logger.info("尝试只满足单个最高权重羁绊,生成更多候选阵容") + highest_priority_synergy = min_chess_options[0] + + for combo in highest_priority_synergy: + candidate = TeamComposition() + for chess in base_team.chess_list: + candidate.add_chess(chess) + + for chess in combo: + candidate.add_chess(chess) + + if candidate.size <= population: + if candidate.size < population: + remaining_slots = population - candidate.size + filled_candidates = self._fill_team_with_best_chess(candidate, remaining_slots, multiple_variants=True) + candidate_teams.extend(filled_candidates) + else: + candidate_teams.append(candidate) + + # 如果经过所有尝试仍然没有生成候选阵容,使用基础阵容加上填充 + if not candidate_teams: + logger.info("无法满足任何羁绊要求,直接填充最佳棋子") + remaining_slots = population - base_team.size + return self._fill_team_with_best_chess(base_team, remaining_slots, multiple_variants=True) + return candidate_teams def _fill_team_with_best_chess( self, base_team: TeamComposition, - remaining_slots: int + remaining_slots: int, + multiple_variants: bool = False ) -> List[TeamComposition]: """ 用最佳棋子填充剩余槽位 @@ -362,6 +527,7 @@ class RecommendationEngine: Args: base_team: 基础阵容 remaining_slots: 剩余槽位数量 + multiple_variants: 是否生成多个变种阵容 Returns: List[TeamComposition]: 填充后的阵容列表 @@ -401,43 +567,72 @@ class RecommendationEngine: value += cost * cost_multiplier # 根据羁绊评估额外价值 + synergy_contribution = 0 + potential_new_level = False + for job_id in chess.get('jobIds', '').split(','): - if job_id and job_id in base_synergies: + if job_id: # 如果能与基础阵容形成羁绊,增加价值 synergy = self.api.get_synergy_by_id(job_id) - if synergy: + if not synergy: + continue + + if job_id in base_synergies: # 检查是否能激活新的羁绊等级 levels = self.api.get_synergy_levels(job_id) current_count = base_synergies[job_id] + for level_str in sorted(levels.keys()): level = int(level_str) if current_count + 1 >= level > current_count: # 如果添加这个棋子后能激活新的等级 weight = self.weights_config.get('synergy_weights', {}).get(synergy.get('name', ''), 1.0) level_multiplier = float(self.weights_config.get('synergy_level_weights', {}).get(level_str, 1.0)) - value += weight * level_multiplier * 10 + synergy_contribution += weight * level_multiplier * 10 + potential_new_level = True break + # 即使不能激活新等级,也增加一些价值 - value += 1 + synergy_contribution += 2 + else: + # 新的羁绊,给予一些价值 + weight = self.weights_config.get('synergy_weights', {}).get(synergy.get('name', ''), 1.0) + synergy_contribution += weight * 3 for race_id in chess.get('raceIds', '').split(','): - if race_id and race_id in base_synergies: + if race_id: # 如果能与基础阵容形成羁绊,增加价值 synergy = self.api.get_synergy_by_id(race_id) - if synergy: + if not synergy: + continue + + if race_id in base_synergies: # 检查是否能激活新的羁绊等级 levels = self.api.get_synergy_levels(race_id) current_count = base_synergies[race_id] + for level_str in sorted(levels.keys()): level = int(level_str) if current_count + 1 >= level > current_count: # 如果添加这个棋子后能激活新的等级 weight = self.weights_config.get('synergy_weights', {}).get(synergy.get('name', ''), 1.0) level_multiplier = float(self.weights_config.get('synergy_level_weights', {}).get(level_str, 1.0)) - value += weight * level_multiplier * 10 + synergy_contribution += weight * level_multiplier * 10 + potential_new_level = True break + # 即使不能激活新等级,也增加一些价值 - value += 1 + synergy_contribution += 2 + else: + # 新的羁绊,给予一些价值 + weight = self.weights_config.get('synergy_weights', {}).get(synergy.get('name', ''), 1.0) + synergy_contribution += weight * 3 + + # 如果棋子能激活新的羁绊等级,给予额外加成 + if potential_new_level: + value += synergy_contribution * 1.5 + else: + value += synergy_contribution # 添加棋子自定义权重 chess_weight = self.weights_config.get('chess_weights', {}).get(chess.get('displayName', ''), 1.0) @@ -448,14 +643,880 @@ class RecommendationEngine: # 按价值从高到低排序 chess_values.sort(key=lambda x: x[1], reverse=True) - # 创建候选阵容,添加价值最高的棋子 - candidate = TeamComposition() - # 添加基础阵容的棋子 + if not multiple_variants: + # 单一变种模式:创建一个最优阵容 + candidate = TeamComposition() + # 添加基础阵容的棋子 + for chess in base_team.chess_list: + candidate.add_chess(chess) + + # 添加价值最高的剩余槽位数量的棋子 + for i in range(min(remaining_slots, len(chess_values))): + candidate.add_chess(chess_values[i][0]) + + return [candidate] + else: + # 多变种模式:创建多个不同的阵容组合 + candidates = [] + + # 1. 添加基于纯价值排序的标准阵容 + standard_candidate = TeamComposition() + for chess in base_team.chess_list: + standard_candidate.add_chess(chess) + + for i in range(min(remaining_slots, len(chess_values))): + standard_candidate.add_chess(chess_values[i][0]) + + candidates.append(standard_candidate) + + # 如果剩余槽位较少或棋子不足,就不生成更多变种 + if remaining_slots <= 1 or len(chess_values) < remaining_slots * 2: + return candidates + + # 2. 添加基于不同羁绊侧重的变种阵容 + # 识别基础阵容中最有潜力的羁绊 + potential_synergies = {} + for synergy_id, count in base_synergies.items(): + # 获取该羁绊的信息 + synergy = self.api.get_synergy_by_id(synergy_id) + if not synergy: + continue + + # 获取该羁绊的等级 + levels = self.api.get_synergy_levels(synergy_id) + next_level = None + + # 寻找下一个可能的等级 + for level_str in sorted(levels.keys(), key=int): + level = int(level_str) + if level > count: + next_level = level + break + + if next_level is not None: + # 计算距离下一级需要的棋子数 + needed = next_level - count + # 如果在剩余槽位内可以实现,则视为潜力羁绊 + if needed <= remaining_slots: + synergy_name = synergy.get('name', '') + weight = self.weights_config.get('synergy_weights', {}).get(synergy_name, 1.0) + potential_synergies[synergy_id] = { + 'name': synergy_name, + 'needed': needed, + 'weight': weight, + 'next_level': next_level + } + + # 按权重和距离下一级的需求排序 + sorted_potentials = sorted( + potential_synergies.items(), + key=lambda x: (x[1]['weight'] / max(1, x[1]['needed']), -x[1]['needed']), + reverse=True + ) + + # 为每个高潜力羁绊创建一个变种 + max_variants = min(5, len(sorted_potentials)) + for i in range(min(max_variants, len(sorted_potentials))): + synergy_id, synergy_info = sorted_potentials[i] + + # 寻找能提供该羁绊的棋子 + synergy_chess = [] + for chess, _ in chess_values: + if (synergy_id in chess.get('jobIds', '').split(',') or + synergy_id in chess.get('raceIds', '').split(',')): + synergy_chess.append(chess) + + if len(synergy_chess) >= synergy_info['needed']: + # 创建新的变种阵容 + variant = TeamComposition() + for chess in base_team.chess_list: + variant.add_chess(chess) + + # 添加强化特定羁绊的棋子 + for j in range(min(synergy_info['needed'], len(synergy_chess))): + variant.add_chess(synergy_chess[j]) + + # 如果还有剩余槽位,填充价值最高的其他棋子 + remaining = remaining_slots - synergy_info['needed'] + if remaining > 0: + # 排除已添加的棋子 + added_chess_names = {chess.get('displayName') for chess in variant.chess_list} + for chess, _ in chess_values: + if (chess.get('displayName') not in added_chess_names and + remaining > 0): + variant.add_chess(chess) + remaining -= 1 + + candidates.append(variant) + + # 3. 基于费用分布的变种 + if remaining_slots >= 3: # 只有在有足够槽位时才考虑费用分布 + # 高费用变种 + high_cost_variant = TeamComposition() + for chess in base_team.chess_list: + high_cost_variant.add_chess(chess) + + # 按费用从高到低排序 + cost_sorted_chess = sorted( + available_chess, + key=lambda c: (int(c.get('price', 0)), + sum(self.weights_config.get('chess_weights', {}).get(c.get('displayName', ''), 1.0) for c in base_team.chess_list)), + reverse=True + ) + + for i in range(min(remaining_slots, len(cost_sorted_chess))): + high_cost_variant.add_chess(cost_sorted_chess[i]) + + candidates.append(high_cost_variant) + + return candidates + + def _generate_teams_greedy( + self, + base_team: TeamComposition, + population: int, + all_synergy_levels: Optional[Dict[str, int]] = None + ) -> List[TeamComposition]: + """ + 使用贪婪算法生成阵容,尝试激活尽可能多的高价值羁绊 + + Args: + base_team: 基础阵容 + population: 人口上限 + all_synergy_levels: 尝试激活的羁绊及其等级,格式为 {synergy_id: min_level} + + Returns: + List[TeamComposition]: 生成的阵容列表 + """ + logger.info("使用贪婪算法生成阵容") + + # 获取所有可用棋子 + all_chess = self.api.get_all_chess() + base_chess_names = {chess.get('displayName') for chess in base_team.chess_list} + available_chess = [chess for chess in all_chess + if chess.get('displayName') not in base_chess_names] + + # 计算基础阵容中已有的羁绊 + base_synergies = {} for chess in base_team.chess_list: - candidate.add_chess(chess) + # 职业 + for job_id in chess.get('jobIds', '').split(','): + if job_id: + base_synergies[job_id] = base_synergies.get(job_id, 0) + 1 + + # 特质 + for race_id in chess.get('raceIds', '').split(','): + if race_id: + base_synergies[race_id] = base_synergies.get(race_id, 0) + 1 - # 添加价值最高的剩余槽位数量的棋子 - for i in range(min(remaining_slots, len(chess_values))): - candidate.add_chess(chess_values[i][0]) + # 如果未指定羁绊等级,则计算当前阵容中所有羁绊到下一级的距离 + if not all_synergy_levels: + all_synergy_levels = {} + + # 检查当前羁绊 + for synergy_id, count in base_synergies.items(): + levels = self.api.get_synergy_levels(synergy_id) + next_level = None + + # 寻找下一个等级 + for level_str in sorted(levels.keys(), key=int): + level = int(level_str) + if level > count: + next_level = level + break + + if next_level is not None: + all_synergy_levels[synergy_id] = next_level + + # 检查其他可能的羁绊 + all_synergies = self.api.get_all_synergies() + for synergy in all_synergies: + synergy_id = synergy.get('jobId') or synergy.get('raceId') + if synergy_id and synergy_id not in all_synergy_levels: + levels = self.api.get_synergy_levels(synergy_id) + if levels: + # 取最低等级 + lowest_level = min(int(lvl) for lvl in levels.keys()) + all_synergy_levels[synergy_id] = lowest_level - return [candidate] \ No newline at end of file + # 为每个羁绊分配权重 + synergy_weights = {} + for synergy_id, target_level in all_synergy_levels.items(): + synergy = self.api.get_synergy_by_id(synergy_id) + if not synergy: + continue + + name = synergy.get('name', '') + weight = self.weights_config.get('synergy_weights', {}).get(name, 1.0) + level_multiplier = self.weights_config.get('synergy_level_weights', {}).get(str(target_level), 1.0) + + # 计算激活该羁绊所需的成本(还需要的棋子数) + current_count = base_synergies.get(synergy_id, 0) + needed = max(0, target_level - current_count) + + # 根据权重、等级和成本计算综合价值 + if needed > 0: + # 考虑难度系数,需求越多越难 + difficulty_factor = min(1.0, 3 / needed) if needed > 3 else 1.0 + value = weight * level_multiplier * difficulty_factor * 10 + synergy_weights[synergy_id] = (value, needed, target_level) + + # 按价值从高到低排序 + sorted_synergies = sorted( + synergy_weights.items(), + key=lambda x: x[1][0], + reverse=True + ) + + # 生成多个候选阵容,每个从不同角度构建 + candidates = [] + + # 1. 从高价值羁绊开始构建 + for start_idx in range(min(5, len(sorted_synergies))): + # 创建新阵容 + team = TeamComposition() + for chess in base_team.chess_list: + team.add_chess(chess) + + # 获取当前阵容中的棋子 + current_chess_names = {chess.get('displayName') for chess in team.chess_list} + + # 获取当前激活的羁绊 + current_synergies = base_synergies.copy() + + # 从某个高价值羁绊开始 + focused_synergies = sorted_synergies[start_idx:] + sorted_synergies[:start_idx] + + # 尝试添加棋子,直到达到人口限制 + remaining_slots = population - team.size + + while remaining_slots > 0 and focused_synergies: + synergy_id, (_, needed, target_level) = focused_synergies[0] + focused_synergies = focused_synergies[1:] + + # 获取当前羁绊的等级 + current_level = current_synergies.get(synergy_id, 0) + still_needed = max(0, target_level - current_level) + + if still_needed <= 0: + continue + + # 找出所有可以提供该羁绊的棋子 + synergy_chess = [] + for chess in available_chess: + if (synergy_id in chess.get('jobIds', '').split(',') or + synergy_id in chess.get('raceIds', '').split(',')): + if chess.get('displayName') not in current_chess_names: + synergy_chess.append(chess) + + # 按价值和费用排序(优先低费用) + synergy_chess.sort(key=lambda c: ( + self.weights_config.get('chess_weights', {}).get(c.get('displayName', ''), 1.0), + -int(c.get('price', 0)) + ), reverse=True) + + # 添加所需的棋子 + added = 0 + for chess in synergy_chess: + if remaining_slots <= 0: + break + + if added >= still_needed: + break + + # 添加棋子 + team.add_chess(chess) + current_chess_names.add(chess.get('displayName')) + remaining_slots -= 1 + added += 1 + + # 更新羁绊计数 + for job_id in chess.get('jobIds', '').split(','): + if job_id: + current_synergies[job_id] = current_synergies.get(job_id, 0) + 1 + + for race_id in chess.get('raceIds', '').split(','): + if race_id: + current_synergies[race_id] = current_synergies.get(race_id, 0) + 1 + + # 如果还有剩余槽位,填充价值最高的棋子 + if remaining_slots > 0: + # 评估剩余棋子 + available = [c for c in available_chess if c.get('displayName') not in current_chess_names] + + # 计算每个棋子的价值 + chess_values = [] + for chess in available: + value = 0 + + # 基础价值 + cost = int(chess.get('price', 1)) + cost_multiplier = float(self.weights_config.get('cost_weights', {}).get(str(cost), 1.0)) + value += cost * cost_multiplier + + # 羁绊价值 + for job_id in chess.get('jobIds', '').split(','): + if job_id and job_id in current_synergies: + synergy = self.api.get_synergy_by_id(job_id) + if synergy: + levels = self.api.get_synergy_levels(job_id) + current = current_synergies[job_id] + + # 检查是否能激活新等级 + for level_str in sorted(levels.keys()): + level = int(level_str) + if current + 1 >= level > current: + value += 20 + break + value += 2 + + for race_id in chess.get('raceIds', '').split(','): + if race_id and race_id in current_synergies: + synergy = self.api.get_synergy_by_id(race_id) + if synergy: + levels = self.api.get_synergy_levels(race_id) + current = current_synergies[race_id] + + # 检查是否能激活新等级 + for level_str in sorted(levels.keys()): + level = int(level_str) + if current + 1 >= level > current: + value += 20 + break + value += 2 + + # 棋子权重 + chess_weight = self.weights_config.get('chess_weights', {}).get(chess.get('displayName', ''), 1.0) + value *= chess_weight + + chess_values.append((chess, value)) + + # 排序并添加 + chess_values.sort(key=lambda x: x[1], reverse=True) + + for i in range(min(remaining_slots, len(chess_values))): + team.add_chess(chess_values[i][0]) + + candidates.append(team) + + # 2. 基于羁绊组合的方式构建阵容 + # 选择最有潜力的几个羁绊组合 + potential_synergy_combinations = [] + + # 获取所有潜在的羁绊对 + synergy_pairs = [] + for i in range(len(sorted_synergies)): + for j in range(i+1, len(sorted_synergies)): + syn1_id, (val1, needed1, _) = sorted_synergies[i] + syn2_id, (val2, needed2, _) = sorted_synergies[j] + + # 如果两个羁绊一起的需求不超过人口上限 + if needed1 + needed2 <= population - base_team.size: + total_value = val1 + val2 + synergy_pairs.append((syn1_id, syn2_id, total_value)) + + # 为每个有潜力的羁绊对构建阵容 + synergy_pairs.sort(key=lambda x: x[2], reverse=True) + + for pair_idx in range(min(5, len(synergy_pairs))): + syn1_id, syn2_id, _ = synergy_pairs[pair_idx] + + # 创建新阵容 + team = TeamComposition() + for chess in base_team.chess_list: + team.add_chess(chess) + + # 获取当前阵容中的棋子 + current_chess_names = {chess.get('displayName') for chess in team.chess_list} + + # 获取当前激活的羁绊 + current_synergies = base_synergies.copy() + + # 先处理这两个羁绊 + priority_synergies = [ + (syn1_id, synergy_weights[syn1_id]), + (syn2_id, synergy_weights[syn2_id]) + ] + + # 剩余的羁绊 + other_synergies = [s for s in sorted_synergies if s[0] not in {syn1_id, syn2_id}] + + # 所有羁绊顺序 + focused_synergies = priority_synergies + other_synergies + + # 尝试添加棋子,直到达到人口限制 + remaining_slots = population - team.size + + while remaining_slots > 0 and focused_synergies: + synergy_id, (_, needed, target_level) = focused_synergies[0] + focused_synergies = focused_synergies[1:] + + # 获取当前羁绊的等级 + current_level = current_synergies.get(synergy_id, 0) + still_needed = max(0, target_level - current_level) + + if still_needed <= 0: + continue + + # 找出所有可以提供该羁绊的棋子 + synergy_chess = [] + for chess in available_chess: + if (synergy_id in chess.get('jobIds', '').split(',') or + synergy_id in chess.get('raceIds', '').split(',')): + if chess.get('displayName') not in current_chess_names: + synergy_chess.append(chess) + + # 按价值和费用排序 + synergy_chess.sort(key=lambda c: ( + self.weights_config.get('chess_weights', {}).get(c.get('displayName', ''), 1.0), + -int(c.get('price', 0)) + ), reverse=True) + + # 添加所需的棋子 + added = 0 + for chess in synergy_chess: + if remaining_slots <= 0: + break + + if added >= still_needed: + break + + # 添加棋子 + team.add_chess(chess) + current_chess_names.add(chess.get('displayName')) + remaining_slots -= 1 + added += 1 + + # 更新羁绊计数 + for job_id in chess.get('jobIds', '').split(','): + if job_id: + current_synergies[job_id] = current_synergies.get(job_id, 0) + 1 + + for race_id in chess.get('raceIds', '').split(','): + if race_id: + current_synergies[race_id] = current_synergies.get(race_id, 0) + 1 + + # 如果还有剩余槽位,填充价值最高的棋子 + if remaining_slots > 0: + # 评估剩余棋子 + available = [c for c in available_chess if c.get('displayName') not in current_chess_names] + + # 计算每个棋子的价值 + chess_values = [] + for chess in available: + value = 0 + + # 基础价值 + cost = int(chess.get('price', 1)) + cost_multiplier = float(self.weights_config.get('cost_weights', {}).get(str(cost), 1.0)) + value += cost * cost_multiplier + + # 羁绊价值 + for job_id in chess.get('jobIds', '').split(','): + if job_id and job_id in current_synergies: + synergy = self.api.get_synergy_by_id(job_id) + if synergy: + levels = self.api.get_synergy_levels(job_id) + current = current_synergies[job_id] + + # 检查是否能激活新等级 + for level_str in sorted(levels.keys()): + level = int(level_str) + if current + 1 >= level > current: + value += 20 + break + value += 2 + + for race_id in chess.get('raceIds', '').split(','): + if race_id and race_id in current_synergies: + synergy = self.api.get_synergy_by_id(race_id) + if synergy: + levels = self.api.get_synergy_levels(race_id) + current = current_synergies[race_id] + + # 检查是否能激活新等级 + for level_str in sorted(levels.keys()): + level = int(level_str) + if current + 1 >= level > current: + value += 20 + break + value += 2 + + # 棋子权重 + chess_weight = self.weights_config.get('chess_weights', {}).get(chess.get('displayName', ''), 1.0) + value *= chess_weight + + chess_values.append((chess, value)) + + # 排序并添加 + chess_values.sort(key=lambda x: x[1], reverse=True) + + for i in range(min(remaining_slots, len(chess_values))): + team.add_chess(chess_values[i][0]) + + candidates.append(team) + + return candidates + + def _generate_teams_exhaustive( + self, + base_team: TeamComposition, + population: int, + target_synergies: Optional[Dict[str, int]] = None, + max_teams: int = 20 + ) -> List[TeamComposition]: + """ + 使用穷举+剪枝算法生成阵容,尝试各种组合并保留最佳结果 + + Args: + base_team: 基础阵容 + population: 人口上限 + target_synergies: 目标羁绊及等级,格式为 {synergy_id: min_level} + max_teams: 最多生成的阵容数量 + + Returns: + List[TeamComposition]: 生成的阵容列表 + """ + logger.info("使用穷举+剪枝算法生成阵容") + + # 获取所有可用棋子 + all_chess = self.api.get_all_chess() + base_chess_names = {chess.get('displayName') for chess in base_team.chess_list} + available_chess = [chess for chess in all_chess + if chess.get('displayName') not in base_chess_names] + + # 计算基础阵容中已有的羁绊 + base_synergies = {} + for chess in base_team.chess_list: + # 职业 + for job_id in chess.get('jobIds', '').split(','): + if job_id: + base_synergies[job_id] = base_synergies.get(job_id, 0) + 1 + + # 特质 + for race_id in chess.get('raceIds', '').split(','): + if race_id: + base_synergies[race_id] = base_synergies.get(race_id, 0) + 1 + + # 如果未指定目标羁绊,使用所有羁绊的最低等级作为目标 + if not target_synergies: + target_synergies = {} + + # 先考虑已有羁绊的提升 + for synergy_id, count in base_synergies.items(): + levels = self.api.get_synergy_levels(synergy_id) + next_level = None + + for level_str in sorted(levels.keys(), key=int): + level = int(level_str) + if level > count: + next_level = level + break + + if next_level is not None: + target_synergies[synergy_id] = next_level + + # 再考虑其他羁绊 + if not target_synergies: + all_synergies = self.api.get_all_synergies() + for synergy in all_synergies: + synergy_id = synergy.get('jobId') or synergy.get('raceId') + if synergy_id and synergy_id not in target_synergies: + levels = self.api.get_synergy_levels(synergy_id) + if levels: + # 取最低等级 + lowest_level = min(int(lvl) for lvl in levels.keys()) + target_synergies[synergy_id] = lowest_level + + # 对棋子进行价值评估 + chess_values = [] + for chess in available_chess: + # 基础价值 + value = 0 + cost = int(chess.get('price', 1)) + cost_multiplier = float(self.weights_config.get('cost_weights', {}).get(str(cost), 1.0)) + value += cost * cost_multiplier + + # 羁绊价值 + for job_id in chess.get('jobIds', '').split(','): + if job_id: + if job_id in target_synergies: + # 如果是目标羁绊,增加高价值 + synergy = self.api.get_synergy_by_id(job_id) + if synergy: + name = synergy.get('name', '') + weight = self.weights_config.get('synergy_weights', {}).get(name, 1.0) + value += weight * 10 + elif job_id in base_synergies: + # 如果是已有羁绊,增加一些价值 + value += 5 + + for race_id in chess.get('raceIds', '').split(','): + if race_id: + if race_id in target_synergies: + # 如果是目标羁绊,增加高价值 + synergy = self.api.get_synergy_by_id(race_id) + if synergy: + name = synergy.get('name', '') + weight = self.weights_config.get('synergy_weights', {}).get(name, 1.0) + value += weight * 10 + elif race_id in base_synergies: + # 如果是已有羁绊,增加一些价值 + value += 5 + + # 棋子权重 + chess_weight = self.weights_config.get('chess_weights', {}).get(chess.get('displayName', ''), 1.0) + value *= chess_weight + + chess_values.append((chess, value)) + + # 按价值从高到低排序 + chess_values.sort(key=lambda x: x[1], reverse=True) + top_chess = [chess for chess, _ in chess_values[:min(20, len(chess_values))]] + + # 剩余需要添加的棋子数量 + remaining_slots = population - base_team.size + + # 如果剩余槽位较小,可以直接穷举所有组合 + if remaining_slots <= 4 and len(top_chess) <= 15: + logger.info(f"剩余槽位较少,进行完全穷举 (槽位: {remaining_slots}, 棋子: {len(top_chess)})") + return self._exhaustive_search(base_team, top_chess, remaining_slots, target_synergies, max_teams) + + # 否则使用贪婪搜索 + 部分穷举 + logger.info(f"剩余槽位较多,使用贪婪搜索 + 部分穷举 (槽位: {remaining_slots})") + + # 生成候选阵容列表 + candidate_teams = [] + + # 1. 先使用贪婪算法生成一些候选阵容 + greedy_teams = [] + + # 基本贪婪阵容 + basic_team = TeamComposition() + for chess in base_team.chess_list: + basic_team.add_chess(chess) + + # 添加价值最高的棋子 + for i in range(min(remaining_slots, len(top_chess))): + basic_team.add_chess(top_chess[i]) + + greedy_teams.append(basic_team) + + # 为每个目标羁绊生成一个侧重阵容 + for synergy_id, target_level in target_synergies.items(): + # 计算当前等级 + current_level = base_synergies.get(synergy_id, 0) + needed = max(0, target_level - current_level) + + if needed <= 0 or needed > remaining_slots: + continue + + # 找出能提供该羁绊的棋子 + synergy_chess = [] + for chess in available_chess: + if (synergy_id in chess.get('jobIds', '').split(',') or + synergy_id in chess.get('raceIds', '').split(',')): + synergy_chess.append(chess) + + if len(synergy_chess) < needed: + continue + + # 按价值排序 + synergy_chess.sort(key=lambda c: next((v for ch, v in chess_values if ch.get('displayName') == c.get('displayName')), 0), reverse=True) + + # 创建新阵容 + team = TeamComposition() + for chess in base_team.chess_list: + team.add_chess(chess) + + # 添加羁绊棋子 + for i in range(min(needed, len(synergy_chess))): + team.add_chess(synergy_chess[i]) + + # 如果还有剩余槽位,填充价值最高的棋子 + remaining = remaining_slots - needed + if remaining > 0: + added_chess = {chess.get('displayName') for chess in team.chess_list} + + for chess, _ in chess_values: + if remaining <= 0: + break + + if chess.get('displayName') not in added_chess: + team.add_chess(chess) + remaining -= 1 + + greedy_teams.append(team) + + candidate_teams.extend(greedy_teams) + + # 2. 针对部分关键位置进行穷举搜索 + # 如果剩余槽位较多,可以固定一部分高价值棋子,对剩余槽位进行穷举 + if remaining_slots > 4: + # 固定一部分高价值棋子 + fixed_count = remaining_slots - 4 # 留出4个槽位进行穷举 + + # 创建基础阵容 + partial_base = TeamComposition() + for chess in base_team.chess_list: + partial_base.add_chess(chess) + + # 添加固定的高价值棋子 + for i in range(min(fixed_count, len(top_chess))): + partial_base.add_chess(top_chess[i]) + + # 剩余可选棋子 + added_chess = {chess.get('displayName') for chess in partial_base.chess_list} + remaining_chess = [chess for chess in top_chess if chess.get('displayName') not in added_chess] + + # 进行局部穷举 + partial_exhaustive = self._exhaustive_search(partial_base, remaining_chess, remaining_slots - fixed_count, target_synergies, max_teams // 2) + candidate_teams.extend(partial_exhaustive) + + # 3. 随机生成一些阵容以增加多样性 + import random + random_teams = [] + + for _ in range(5): # 生成5个随机阵容 + random_team = TeamComposition() + for chess in base_team.chess_list: + random_team.add_chess(chess) + + added_chess = {chess.get('displayName') for chess in random_team.chess_list} + random_selection = random.sample(available_chess, min(remaining_slots, len(available_chess))) + + for chess in random_selection: + if chess.get('displayName') not in added_chess: + random_team.add_chess(chess) + + random_teams.append(random_team) + + candidate_teams.extend(random_teams) + + # 确保结果不超过最大阵容数 + return candidate_teams[:max_teams] + + def _exhaustive_search( + self, + base_team: TeamComposition, + available_chess: List[Dict[str, Any]], + slots: int, + target_synergies: Dict[str, int], + max_teams: int + ) -> List[TeamComposition]: + """ + 针对给定的基础阵容和可用棋子,穷举所有可能的组合 + + Args: + base_team: 基础阵容 + available_chess: 可用的棋子列表 + slots: 剩余槽位数 + target_synergies: 目标羁绊及等级 + max_teams: 最多返回的阵容数量 + + Returns: + List[TeamComposition]: 生成的阵容列表 + """ + # 如果可用棋子太多,进行剪枝 + if len(available_chess) > 12 and slots < 5: + # 按价值评估缩小棋子池 + chess_values = [] + for chess in available_chess: + value = 0 + + # 检查是否能激活目标羁绊 + for job_id in chess.get('jobIds', '').split(','): + if job_id and job_id in target_synergies: + value += 10 + + for race_id in chess.get('raceIds', '').split(','): + if race_id and race_id in target_synergies: + value += 10 + + # 基础价值 + cost = int(chess.get('price', 1)) + value += cost + + # 棋子权重 + chess_weight = self.weights_config.get('chess_weights', {}).get(chess.get('displayName', ''), 1.0) + value *= chess_weight + + chess_values.append((chess, value)) + + # 按价值从高到低排序 + chess_values.sort(key=lambda x: x[1], reverse=True) + + # 取前N个 + available_chess = [chess for chess, _ in chess_values[:min(12, len(chess_values))]] + + # 计算基础阵容中已有的羁绊 + base_synergies = {} + for chess in base_team.chess_list: + # 职业 + for job_id in chess.get('jobIds', '').split(','): + if job_id: + base_synergies[job_id] = base_synergies.get(job_id, 0) + 1 + + # 特质 + for race_id in chess.get('raceIds', '').split(','): + if race_id: + base_synergies[race_id] = base_synergies.get(race_id, 0) + 1 + + # 生成所有可能的组合 + candidate_teams = [] + + # 如果槽位数为0,直接返回基础阵容 + if slots == 0: + return [base_team] + + # 生成所有slots大小的组合 + import itertools + combinations = list(itertools.combinations(available_chess, min(slots, len(available_chess)))) + + # 如果组合太多,进行采样 + max_combinations = 1000 + if len(combinations) > max_combinations: + import random + combinations = random.sample(combinations, max_combinations) + + # 用各种组合生成阵容 + for combo in combinations: + team = TeamComposition() + + # 添加基础阵容 + for chess in base_team.chess_list: + team.add_chess(chess) + + # 添加组合中的棋子 + for chess in combo: + team.add_chess(chess) + + # 计算羁绊 + synergy_counts = base_synergies.copy() + + for chess in combo: + # 职业 + for job_id in chess.get('jobIds', '').split(','): + if job_id: + synergy_counts[job_id] = synergy_counts.get(job_id, 0) + 1 + + # 特质 + for race_id in chess.get('raceIds', '').split(','): + if race_id: + synergy_counts[race_id] = synergy_counts.get(race_id, 0) + 1 + + # 计算能达到的目标羁绊数量 + achieved_targets = 0 + for synergy_id, target_level in target_synergies.items(): + if synergy_counts.get(synergy_id, 0) >= target_level: + achieved_targets += 1 + + # 存储信息供后续排序使用 + team.score = achieved_targets + candidate_teams.append(team) + + # 按达成的目标数量从高到低排序 + candidate_teams.sort(key=lambda t: t.score, reverse=True) + + # 取前max_teams个结果 + return candidate_teams[:max_teams] \ No newline at end of file