52 KiB
需求:匹配与 AI 决策
状态:正式需求(匹配 + A0 背包刷新已接线;决策细则继续完善)
日期:2026-09-01(实现入口更新:2026-09-03)
来源:策划脑图「西游对战 ai」+ 架构讨论确认
关联:需求-新项目概述.md、需求-背包刷新.md、方案-出怪逻辑实现.md、开发规范.md
1. 目标与范围
| 项 | 说明 |
|---|---|
| 目标 | 玩家 vs AI(不做联机):开战前匹配对手档案;局内 AI 按规则刷新/拖动/落子/用道具,与玩家共用同一套战斗与合成规则 |
| 形态 | 同图双阵营(CampAreaID + CampAreaPolicy=pvp);UI 上下仅为表现,逻辑以 playerId / 阵营隔离 |
| 本机 | playerId=0:玩家操作(既有背包 UI,「召唤」= 普通刷新) |
| 对手 | playerId=1:AI 操作者(无头背包 + 决策,无第二套完整拖拽 UI 亦可) |
| 本期不做 | 真联机 PVP、搜索/学习型 AI、复活半图镜像 |
| 待玩家侧先实现再同步 AI | §8 主动技能、§8.2 广告、§9 装备掉落、桃子击杀来源(玩家侧)、胜负条件——规则先定稿,依赖项落地后再接 |
1.0 实现入口(先做这个)
局内 AI 从「背包刷新」切入,对齐玩家底栏「召唤」:扣桃 → 字池抽满 5 槽。匹配 / 拖拽决策 / 武将武器等后置。
| 顺序 | 切片 | 范围 | 验收一眼看 |
|---|---|---|---|
| A0 | AI 背包刷新 | §5.1~§5.4:AiHeadlessBagHost + 对手 rankId 字池 + AI 槽扣桃 tryNormalRefresh;首轮自动刷一次 |
AI 有独立 5 槽;扣 Const 4/5;与玩家池 remaining 隔离 |
| A1 | 匹配档案 | §4 Const 11/12/14 + §4.4 buff 挂载(不生效) | 产出 AiMatchProfile,开战传给 A0 建池 |
| A2 | 工程对称化 | §2 / §13.3.9:camp 放置、DeploySync(playerId=1)、AI 槽金 |
AI 刷新后的字能落 AI camp(为 A3 铺路) |
| A3 | 局内决策 | §5.5 tick + §6~§7 + §5.6 武将武器 | 1 秒决策链;合成后挂武器 |
| B~D | 见 §1.2 | 击杀加桃、胜负细项、主动技/广告/掉落 | 依赖玩家侧 |
A0 不依赖完整匹配 UI:对手 rankId 可暂用 RankDataManager.getPlayerRankId() 或已有 pendingOpponentRankId;A1 落地后再换 AiMatchProfile.opponentRankId。
1.2 实现分期(总览)
| 阶段 | 范围 | 说明 |
|---|---|---|
| A | 先 A0 背包刷新 → A1 匹配 → A2 对称化 → A3 §5.5~§7 / §5.6 | 不依赖胜负结算 |
| B | 击杀加桃(玩家+AI)、胜负条件(§10) | 玩家侧先通 |
| C | 连胜/连败存档 + Const 15~18 匹配修正(§4.3/§4.5) | 已接线:MatchStreakStats + 胜负 UI 结算;匹配读 streak |
| D | §8 主动技能、§8.2 广告、§9 装备掉落、buff 效果 | 各玩家能力就绪后 AI 同步 |
1.1 与既有文档关系
| 文档 | 关系 |
|---|---|
| 需求-背包刷新.md | 玩家「召唤」刷新算法已定;A0 复用加权抽字,AI 传入 对手段位 rankId 建池;不含玩家连败组合字保底 |
| 需求-新项目概述.md | 概述中「匹配与 AI」条目由本文承接 |
方案整理中的 AiOpponentController |
实现侧建议类名/模块指向,本文为需求契约 |
2. 架构约束(实现须遵守)
匹配 MatchProfile(段位、buff 数、策略种子)
↓
共享 MX 战斗内核(双 PlayerSlot / 双 port / CampAreaPolicy)
↓
┌───────────────────┬───────────────────┐
│ PlayerController │ AiOpponentController │
│ 背包 UI + 拖拽 │ 无头 Bag + 决策 tick │
└─────────┬─────────┴─────────┬─────────┘
└──── Merge / deploy(playerId) ────┘
| 规则 | 要求 |
|---|---|
| 规则对称 | 刷新费用、合成、落子合法性、城墙、经济按 playerId;禁止写死仅本机 |
| 输入不对称 | 仅本机绑定拖拽 UI;AI 输出动作落到同一套 Merge/deploy |
| 字池 | AI 使用与玩家相同的建池/抽取逻辑,传入对手段位 rankId |
| 广告 | AI 不播激励视频;仅按概率判定是否获得广告类效果 |
| 音效 | 仅本机播;AI 侧武将普攻/技能/命中不播(BattleAudio 按 Team.ownerId) |
| 异常 | 配表缺失、权重全 0、字池耗尽等须 warn,禁止静默兜底糊弄 |
开战前须对称化的耦合点(实现前置,非 AI 算法本身): 放置 camp 校验、DeploySync 按 playerId、墙血/胜负/波次金币作用到对应槽、远端/AI 无头 bagSession。
3. 配表契约
3.1 Const
路径:assets/bundles/first/config/Const.json(已与策划同步,以下摘自现表)。
| id | 含义 | para1(现网) |
规则 |
|---|---|---|---|
| 11 | 匹配星级偏移权重:少 5 星 → 大于 4 星 | 10,10,10,10,10,10,10,10,10,10 |
10 档,对应相对玩家总星偏移 -5,-4,-3,-2,-1,0,+1,+2,+3,+4 |
| 12 | 对方 主动 道具数量权重(-1,0,+1) |
20,30,10 |
结果 clamp(…, 0, Const**19**) |
| 13 | 攻击力等级系数(既有,与匹配无关) | 1,1.5,2.1,2.73,3.276 |
— |
| 14 | 对方 被动 道具数量权重(-2,-1,0,+1) |
20,30,20,10 |
结果 clamp(…, 0, Const**20**) |
| 15 | 玩家连胜 → 主动「+1」档权重加成(加强 AI) | 5 |
连胜 ≥3 起;每多一场再加一份;玩家主动已达上限(Const19)时,加成改加到 0 档 |
| 16 | 玩家连胜 → 被动「+1」档权重加成(加强 AI) | 5 |
同上;被动已达上限(Const20)时加成改加到 0 档 |
| 17 | 玩家连败 → 主动「-1」档权重加成(削弱 AI) | 5 |
连败 ≥3 起;每多一场再加一份 |
| 18 | 玩家连败 → 被动「-2」档权重加成(削弱 AI) | 5 |
连败 ≥3 起;每多一场再加一份 |
| 19 | 最大主动技能数量 | 2 |
玩家/AI 主动槽与 §4.2 clamp 上界 |
| 20 | 最大被动技能数量 | 6 |
玩家/AI 被动槽与 §4.2 clamp 上界 |
| 4 / 5 | 战斗刷新桃子消耗(初值 / 递增) | 10 / 2 |
AI 普通刷新与玩家同一套费用 |
边界:星偏移后落在非法星、或某档不可达时,该档权重视为 0;若总权重 ≤ 0,须 warn 并 中止匹配(见 §13.3.8 说明;正常配表一般不会触发)。
3.2 Rank(对手行)
| 字段 | AI 用途 |
|---|---|
charactersweight |
AI 字池选档:与玩家相同算法,读 对手段位 行 |
aiweapon |
合成武将首次装备:按品质权重随机 Weapon.id(§5.6);与击杀掉落无关 |
aiusead |
AI「广告类效果」触发概率(万分比) |
aiusechanzi |
AI 铲子使用类型权重:[类型1, 类型2, 类型3](见 §6.2) |
aiusetext |
AI 普通字使用类型权重:[类型1, 类型2](见 §6.3) |
weapondrop / weaponweight |
击杀掉落(见 §9,非拖拽主循环) |
diffcult |
局内难度加值(若战斗侧已消费则沿用) |
3.3 其它表
| 表 | 用途 |
|---|---|
Text |
字池候选与 numberN 上限;type∈{1,2,4} 入池;quality:0白 1绿 2蓝 3紫 4橙(§6.4 取舍) |
Hero |
合成、等级上限、武将判定 |
Battlebuff |
type=1 主动 / type=2 被动;按 weight 加权抽取 id(§4.4);本期仅挂载不生效 |
Level |
战场、初始桃子、出怪;双营共用关卡配置 |
4. 匹配(开战前)
4.0 总原则(已确认)
对手段位与对手 buff 都不是独立随机,一律:
AI 值 = 玩家当前值 + 加权随机得到的偏移
| 项 | 玩家基准 | 偏移权重表 | 结果 |
|---|---|---|---|
| AI 段位/星 | 玩家当前总星 | Const 11(相对星偏移 -5~+4) |
对手星 = 玩家星 + 偏移 → 映射 rankId |
| AI 主动 buff 数 | 玩家本局主动数量 | Const 12(-1,0,+1);Const 15~18 修正权重(§4.3) |
clamp(玩家主动数 + 偏移, 0, Const19) |
| AI 被动 buff 数 | 玩家本局被动数量 | Const 14(-2,-1,0,+1);Const 15~18(§4.3) |
clamp(玩家被动数 + 偏移, 0, Const20) |
连胜/连败(Const 15~18): 已实现。字段 matchWinStreak / matchLoseStreak 跨天不清零;胜负结算(MatchWinStats.record)互斥增减;匹配前 AiMatchService 按份数修正 Const 12/14 权重。
设计意图(已确认): 计数对象是 玩家 的连胜/连败,不是 AI。
| 玩家状态 | 对 AI 的效果 | 现表手段 | 实现 |
|---|---|---|---|
| 连胜 | 加强 AI(对手更容易抽到更多 buff) | Const 15/16 提高「+1」档权重 | ✅ |
| 连败 | 削弱 AI(对手更容易抽到更少 buff) | Const 17/18 提高「−1/−2」档权重 | ✅ |
flowchart TD
PStar[玩家总星] --> W11[Const11 十档权重]
W11 --> RollStar[加权随机星偏移]
RollStar --> AiStar["对手星 = 玩家星 + 偏移"]
PAct[玩家主动数] --> W12[Const12 三档权重]
Streak[连胜/连败] --> Mod1518[Const15~18 修正对应档权重]
Mod1518 --> W12
Mod1518 --> W14
W12 --> RollAct[加权随机数量偏移]
PAct --> SumAct["AI主动数 = clamp(玩家+偏移,0,Const19)"]
RollAct --> SumAct
PPas[玩家被动数] --> W14[Const14 四档权重]
W14 --> RollPas[加权随机数量偏移]
PPas --> SumPas["AI被动数 = clamp(玩家+偏移,0,Const20)"]
RollPas --> SumPas
4.1 对手段位(按星)
- 取玩家 总星
RankDataManager.getPlayerRankStars()(存档GameDoc.rankStars,详见 §13.3.1)。 - 读 Rank 表得到 全局最小星
minStar(首档level[0])、全局最大星maxStar(末档level[1],-1表示无上限时取末档level[0]或表内最大星)。 - 复制 Const 11 十档权重;对每一档偏移
d:若玩家星 + d < minStar或> maxStar,该档 权重视为 0(不参与随机)。 - 在剩余权重中加权随机偏移
d ∈ {−5…+4}。 opponentStars = clamp(玩家星 + d, minStar, maxStar)(不会低于最小星、不会超过最高星)→opponentRankId = resolveTierByStars(opponentStars).id。
实现备注:现网
pickOpponentRankId仍按 段位 id 三档读 Const 11,须改为 总星十档偏移(§13.3.1)。
4.2 对手 buff 数量(AI 带入数量)
- 取玩家本局带入的主动数、被动数(存档/当日道具栏,与道具商店对齐)。
- 复制 Const 12 / 14 各档权重。
- 按连胜/连败用 Const 15~18 修正权重(见 §4.3)。
- 分别加权随机数量偏移;
AI主动数 = clamp(玩家主动数 + 偏移, 0, Const19),
AI被动数 = clamp(玩家被动数 + 偏移, 0, Const20)。 - 按 §4.4 从
Battlebuff表加权抽取 不重复 的 buff id 列表,写入MatchProfile。 - 开战:
BattleBuffCarryStore按playerId=1挂载列表;被动效果与玩家对称生效(基地血、召神/召唤费、行军血/速、敌军减速、塔攻速、还魂/神雷/打断/掘宝/击杀桃等)。主动列表已挂载;CD 满自动释放见 §8.1。
匹配结果写入本局 MatchProfile(对手 rankId/星、主动/被动 数量与 id 列表、随机种子等),进战斗后只读。
4.4 Battlebuff 抽取(已确认)
数量由 §4.2 确定;具体 id 从 Battlebuff 表按类型与权重抽取。
| 项 | 规则 |
|---|---|
| 表 | Battlebuff.json |
| 类型 | type=1 → 主动;type=2 → 被动(type=3 局外功能如人参果加体力,不入局内 AI 池) |
| 候选 | 对应 type、weight > 0,且 functiontype ∈ [2,27] |
| 抽取 | 在候选内按 weight 加权随机,抽满目标个数;同一局不可重复(已抽 id 从候选移除) |
与 aiweapon |
无关联;buff 池与 Rank.aiweapon(装备品质)独立 |
| 效果 | 挂载后被动对称生效;主动列表供 §8.1 自动释放 |
| 失败 | 候选不足以抽满目标数 → warn 并暴露,禁止静默少抽或重复凑数 |
4.3 连胜 / 连败如何改权重(已实现)
结算入口:
UIBattleSuccess/UIBattleFail→MatchWinStats.record→MatchStreakStats;匹配:AiMatchService._applyStreakWeightBonus。
只改「权重值」,不改偏移档位集合: 加权随机 之前,把 Const 15~18 加成加到 Const 12/14 对应档,再抽偏移。15~18 不修正 Const 11。
计数读 玩家 连续 streak(§4.5),非 AI。
| Const | 玩家状态 | 作用 | 效果语义 |
|---|---|---|---|
| 15 | 连胜 | 主动(12)+1 档 += 份数×para1;玩家主动已满 Const19 则加到 0 档 |
加强 AI 主动数 |
| 16 | 连胜 | 被动(14)+1 档同上;被动已满 Const20 则加到 0 档 | 加强 AI 被动数 |
| 17 | 连败 | 主动(12)−1 档 += 份数×para1 |
削弱 AI 主动数 |
| 18 | 连败 | 被动(14)−2 档 += 份数×para1 |
削弱 AI 被动数 |
- 触发:连续场次 ≥ 3;
生效份数 = max(0, 连续场次 - 2)。 - 修正仅本局匹配;不写回 Const 表。
- Const 11 不受 连胜/连败影响。
4.5 连胜 / 连败存档(已实现)
| 项 | 约定 |
|---|---|
| 状态 | ✅ GameDoc.matchWinStreak / matchLoseStreak;MatchStreakStats 读写 |
| 语义 | 连续 胜/负(非 matchWinCount 累计总场) |
| 互斥 | 胜 → 连败清零;负 → 连胜清零 |
| 跨天 | 不重置、不清零 |
| 字段 | matchWinStreak / matchLoseStreak(勿用 matchWinCount/matchLoseCount) |
| 匹配 | 开战前读 streak,Const 15~18 生效 |
5. AI 字池与银币刷新(A0 首期)
实现顺序第一刀。 对齐玩家底栏「召唤」:普通刷新扣桃、字池填满 5 槽。不要求本切片做拖拽落子 UI;AI 侧无头 bag 即可。细则对齐 需求-背包刷新.md。
5.0 A0 范围与验收
| 项 | 约定 |
|---|---|
| 做 | AiHeadlessBagHost:playerId=1 独立 BattleBagSession + PlayerBagTextPool.beginBattle(opponentRankId);开战 首轮自动刷新一次(扣桃);之后按 §5.4 有桃可再刷 |
| 扣费 | 与玩家相同 Const 4/5;读/写 AI 槽 PlayerSlot.gold(禁止只扣 localSlot) |
| 不做 | §6 拖拽决策、§7 落点、§5.6 武器、完整匹配 UI、广告刷新、第二套底栏 UI |
| 对手 rank | A1 前可用临时 opponentRankId;A1 后换成 AiMatchProfile.opponentRankId |
| 手工验收 | 开战后 AI bag 5 槽有字;AI 金减少;玩家再点「召唤」不影响 AI remaining;字池耗尽/rankId 非法须 warn |
开战
→ AiHeadlessBagHost.bind(opponentRankId)
→ 字池 beginBattle(对手 rank)
→ tryNormalRefresh(扣 AI 槽桃子,填满 5 槽) ← 对齐玩家「召唤」
后续(A3)
→ 1s tick:每拍一步动作,无动作且有桃则刷 1 次(§5.4 / §5.5)
5.1 字池(复用玩家逻辑)
现网已有实现可对照:PlayerBagTextPool + 需求-背包刷新.md §4。
| 步骤 | 规则 |
|---|---|
| 建池 | beginBattle(对手 rankId):从该行 charactersweight 等概率抽一个整数 N(1~20),整局固定 |
| 候选 | Text.type∈{1,2,4} 且 weight>0 且 numberN>0 |
| 抽取 | 每次刷新填满 5 槽;最终权重 = weight × remaining;抽出后 remaining-=1,整局累计 |
| 失败 | 未建池/耗尽 → warn + 失败,禁止默默塞默认字 |
确认结论: AI 不另配字池字段;直接读 对手段位 Rank.charactersweight。玩家与 AI 各建一份池实例,互不共享 remaining。
现网注意: BattleBagSession 构造里现写死 beginBattle(玩家 rankId),且 tryNormalRefresh 扣 battle.localSlot —— A0 须包装或扩展为 可传 rankId + 按 playerId 扣金,决策逻辑仍放 ai/,共享文件改动单独小 PR。
5.2 Buff 对刷新的影响
| 项 | 约定 |
|---|---|
| 挂载 | 匹配阶段确定的主动/被动 buff id 写入 BattleBuffCarryStore(playerId=1) |
| 表现 | 被动效果与玩家同管线;主动 UI 可不展示,逻辑层持有列表 |
| 效果 | 被动已生效(对称 Host);主动自动释放见 §8.1 |
5.3 桃子(银币)经济
| 项 | 规则 |
|---|---|
| 来源 | 击杀 1 只怪 → +1 桃子(双方槽位各自记账,按 playerId / camp 归属) |
| 初始 | 读 Level.startnumber(与现网开战初始金一致) |
| 刷新扣费 | AI 与玩家相同:Const 4/5;首轮自动刷新也扣桃(不免单) |
| 玩家侧 | 桃子击杀来源尚未实现;AI 侧可先按本规则接 PlayerSlot.gold,待玩家对齐后统一管线 |
| 广告刷 | 无(与玩家背包需求一致);广告类见 §8 |
| A0 | 至少保证:初始金可读、刷新扣 AI 槽;击杀 +1 可后置到阶段 B |
5.4 刷新节奏
| 时机 | 行为 |
|---|---|
| 首轮 | 自动刷新一次(扣桃,费用 Const 4/5)—— A0 必做 |
| 一轮动作后 | 完成当前可做的动作后,若 桃子足够 → 按 tick 逐次刷新(每秒最多刷 1 次,直到不够或字池空)—— A3 |
| 桃子不足 | 不刷新,等待后续 击杀 +1 桃;有钱后再刷(不在一轮内强行结束 tick) |
AI 无「广告刷新」入口。A0 可只做「开战首刷」;连刷挂到 AiOpponentController(A3)即可。
5.5 AI 运行时节奏(已确认,A3)
| 项 | 规则 |
|---|---|
| Tick 间隔 | 每 1 秒 触发一次 AiOpponentController.tick()(固定节拍,非每帧) |
| 开战延迟 | 进入战斗后 2 秒 才开始首次决策(避免一进场就摆字) |
| 单次 tick | 最多一步原子操作(铲/升级/合成/重摆一步/互换/落子…);若无可做且有桃则 刷 1 次 |
| 多步重摆 | 跨度 map→map 按拍排队,每秒挪一格字,避免一瞬堆字 |
| 与玩家 | 不同步玩家拖拽;双轨并行 |
| 表现 | 地图新字播简易落下;1 秒节拍控制操作频率 |
5.6 AI 合成武将武器(已确认,A3)
与 §9 击杀掉落无关:
Rank.aiweapon用于 局内首次合成武将 时的武器品质掷骰;weapondrop/weaponweight仍走 §9 击杀掉落管线(阶段 D)。
读 对手段位 Rank.aiweapon(5 个数,对应 白 / 绿 / 蓝 / 紫 / 橙,即 Weapon.quality 0~4)。
AI 首次合成武将 heroId
→ Hero.weapon 限定可穿武器类型(同 Weapon.type,与 gg.equip.canWearType 一致)
→ Rank.aiweapon 加权随机品质 q∈{0..4}
→ 在 Weapon 表中选 type=Hero.weapon 且 quality=q 的行作为装备
→ 写入本局 registry[heroId]=weaponId(整局只掷一次)
再次合成同一 heroId(拆开又合)→ 直接读 registry,不再掷骰
| 项 | 规则 |
|---|---|
| 触发 | 仅合成武将(Hero.herotype===2)第一次进入 AI 阵营(落图 / deploy)时分配 |
| 类型约束 | Hero.weapon ↔ Weapon.type(1 刀 / 2 枪 / 3 棍 / 4 弓);专属 Hero.personalweapon 若 品质与 q 一致且可穿,优先于同品质其它行 |
| 品质掷骰 | 复制 Rank.aiweapon 五档权重;每次独立加权随机 q;权重全 ≤0 → warn + 中止分配(禁止静默默认橙/白) |
| 允许重复 | 不同武将各自按权重掷骰,同一品质 / 同一 Weapon.id 可以重复出现(与 Battlebuff「不重复」无关,不要求去重) |
| 具体 id | 同 type+quality 多行时:专属优先 → 否则 Weapon.id 升序首行(确定性,便于 seed 回放) |
| 无候选 | 该品质下无合法 Weapon → warn + 回退 Hero.startweapon(startweapon<=0 则暴露并跳过武器技能) |
| 局内存档 | AiHeroWeaponRegistry:heroId → weaponId,仅本局;战斗结束清空 |
| 复用 | 仅同一 heroId:拆下再合成、换格再 deploy → 只读 registry,不重新随机(避免同一武将反复掷骰) |
| 战斗装载 | AI 塔 TowerLoadout 解析武器时 不走 gg.equip;改读 AiBattleBridge.getHeroBattleWeaponId(heroId)(集成 PR,见 §11.2) |
| 与 buff | 独立;aiweapon 不参与 Battlebuff 抽取(§4.4) |
flowchart TD
Merge[AI 合成武将 heroId] --> Has{registry 已有?}
Has -->|是| Use[使用已存 weaponId]
Has -->|否| Type[Hero.weapon 定 type]
Type --> Roll[Rank.aiweapon 掷品质 q]
Roll --> Pick[Weapon 表 type+q 选 id]
Pick --> Save[registry 写入]
Save --> Deploy[deploy / TowerLoadout]
Use --> Deploy
6. 文字拖动规则(局内决策核心)
开战时按 对手段位 Rank 行的权重,随机本局策略类型(整局不变),记入 AiBattlePersonality:
| 策略 | 配表字段 | 权重数组含义 |
|---|---|---|
| 铲子使用类型 | Rank.aiusechanzi |
3 个数:类型 1 / 2 / 3 权重 |
| 普通字使用类型 | Rank.aiusetext |
2 个数:类型 1 / 2 权重 |
加权随机选中一个类型后整局固定;权重全 ≤ 0 或数组长度不符须 warn,禁止静默当成某默认类型。
字品质: 读
Text.quality(0 白 / 1 绿 / 2 蓝 / 3 紫 / 4 橙)。空格取舍时:quality===4为橙合成字侧,quality===3为紫合成字侧(通常对应type===2组合字;以表为准)。
6.1 动作优先级(高 → 低,已确认)
- 可合成优先(背包/地图 → 空位成将,或 逆序邻接互换 成正序;可成将候选取阵型分更高;散字已凑齐 Hero.text 时,清开可移动挡格并挪到横向跨度逐步拼装)
- 优先使用铲子(背包有铲且有可铲格时,按 §6.2 本局类型执行)
- 可升级优先:背包内同级单字合成 → 背包→地图 → 地图→地图
- 空位摆放(§7;背包有字且有空 Put 时必须先摆完,避免被重摆占拍)
- 例外(神将贴路): 场上多格合成神将存在更优贴路/贴出口重摆时,优先阵型重摆,暂缓落 1–3 级普通单字 与无攻击功能字;高等级单字与组合散字仍可先摆
- 空位/空格不够、必须取舍时:按 §6.4 合成字相对普通字 的优先级决定留谁、换谁
- 普通字策略(§6.3 本局
aiusetext类型) - 散字去重/替换(仅无空位时):同
Text.id自由散字收回多余份,或背包字换到重复散字格 - 阵型重摆(多格神将优先贴路边 / 路径出口;可清走/互换单格战斗单位,被挤单位优先落到仍可攻击空位;有多格神将时 1–3 级单字不抢路边阵型位;单格单位有空位先挪空位)——排在摆放之后(见上条例外)
- 若本拍无可做动作且有桃,按 §5.4 刷 1 次;无桃则等下一 tick(§5.5)
说明:日常有空位时主路径是 铲 → 升级 → 摆放;神将可贴路时插队重摆,避免 1–3 级单字长期占满路边。
同优先级内扫描顺序(背包从左到右、地图行列)实现时固定一种并保持稳定,避免帧间抖动。
6.2 铲子(aiusechanzi)——全局最高操作优先
读对手 Rank.aiusechanzi,开战加权随机本局类型:
| 类型 | 权重下标 | 行为(与 AutoTable / 策划一致) |
|---|---|---|
| 1 | [0] |
在可铲地块中 随机 选择 |
| 2 | [1] |
逐步扩张:优先纯 Dig(未勾可放置)与已开 Put 相邻可铲格;putText+dig 双标按 可放置优先 当 Put,不当铲目标 |
| 3 | [2] |
贴岛/贴路:优先白格 Dig 与已开邻接,其次路径邻接;边条 Dig 降权 |
| 项 | 规则 |
|---|---|
| 时机 | 无可合成/拼装时再铲:有铲可用则开地;有可成将时先合成 |
| 来源 | 字池刷出的 Text.type=4;广告铲见 §8 |
6.3 普通字(aiusetext)——优先级靠后
读对手 Rank.aiusetext,开战加权随机本局类型;在铲子、升级、合成、以及(必要时)§6.4 取舍之后执行:
| 类型 | 权重下标 | 行为 |
|---|---|---|
| 1(等级优先) | [0] |
全场谁等级高留谁:不盯某一种字,背包/地图上任意普通字,优先保留 局内等级更高 的单位,低的更愿意被挪走或换掉 |
| 2(单字冲级) | [1] |
只盯一种字把它升满;候选来自 格子 + 背包;满级后改盯下一种未满级字(§6.3.1) |
6.3.1 类型 2「单字冲级」说明
含义: AI 在某一时刻认定 一个目标基础字(一个 Text.id,如「刀」),尽量把 这一种字 升到 Hero.level_limit;其它字都为该目标服务。
「每 tick 可换目标字」是什么意思?(不是每次操作都换)
| 概念 | 说明 |
|---|---|
| tick | AI 每 1 秒 一次决策节拍(§5.5) |
| 一次操作 | tick 内的一步:铲一下 / 合成一次 / 落子一次 … |
| 换目标字 | 重新决定「本阶段要冲哪一种 Text.id」 |
不是「每做一步操作就换一个目标字」。
而是「最多 每 1 秒(每个 tick 开始时)重新评估一次要不要换目标」;同一个 tick 里 连续多步操作 共用同一个目标字。
已确认规则:
- 每个 tick 开始时 按下列规则 选/更新 目标字(可与上一 tick 相同,也可换)。
- 该 tick 内所有 §6.3 操作 共用 此目标,直到下一秒下一个 tick。
- 若目标字 已满级 → 从 AI 己方战场格子 + 背包 中的未满级普通字里,改选「当前局内等级最高」的下一种作为新目标;若 均无 未满级普通字,则本 tick 退化为类型 1 行为。
怎么选目标字:
- 扫描 AI 战场格子上 + 背包里 的全部 普通字(
Text.type=1)。同一Text.id多处存在时,以 该 id 当前最高局内等级 参与比较(背包里也可能有高级字)。 - 只保留 局内等级 < level_limit 的字种。
- 取其中 最高局内等级 的字 id 作为目标;并列 → 若在格上则比覆盖分 → 固定扫描序。
选定目标字之后:
| 动作 | 说明 |
|---|---|
| 保留 | 该字 等级最高的那一颗(格上或背包)尽量留在/放到地图好位置 |
| 合成 | 优先 同 id 低级副本 合成升级(背包 ↔ 格子均可) |
| 腾位 | 优先动 其它字 或 同字低级副本,不动该 id 最高等级那颗 |
| 落子 | 若最高等级仅在背包,优先 落到格子 以提升覆盖(§7) |
举例: 格上 3 级「刀」将满级;背包里有 2 级「枪」→ 刀满级后下一目标可选「枪」。若格上 3 级刀、背包 4 级「棍」,则 tick 初就可能以「棍」为目标并优先落子到格上。
6.4 合成字相对普通字(空格不够时的 互换)
仅当 己方可建造空位(Put)不足 时启用。本质是 格子上的单位互换(被换下的进背包),不是凭空覆盖。
背包有铲且仍有 Dig 时由 §6.1 先铲(铲子优先于互换),不走到本条;无铲时允许互换,避免 Dig 未开完、又刷不起背包时长期空转。
编辑防御:地图格若同时勾选 isCanPutText + isCanBeDig,逻辑层 可放置优先(按 Put 入场,不当 Dig);纯 Dig(仅勾挖掘)仍须铲子。
占格优先级(高 → 低,已确认):
2级普通字 > 橙色字(quality=4) > 紫色字(quality=3) > 1级普通字
| 规则 | 说明 |
|---|---|
橙色字(quality=4) |
可挤 1 级普通字、以及 紫色合成字(type=2 且 quality=3);不可挤 2 级普通字 |
紫色字(quality=3) |
仅可挤 1 级普通字(不可挤 2 级普通字;2 级普通高于橙/紫) |
| 选目标 | 有多格可挤时,优先挤更低档:1 级普通 → 紫 |
| 神将 / 多格成将 | 不可被橙/紫互换挤占;挤占成将格会导致 DeploySync 卖掉战斗实体(表现为「合成了但不打」) |
| 被换下单位 | 进入 AI 背包(与 Merge 互换规则一致;互换成对出现,不应出现背包满无法接收) |
| 神将 | 默认不可 被橙/紫字挤占 |
| 神将换神将 | 若新落子是 神将 且格上已有 更低等级神将,允许 高替低(被换下的低级神将 回背包) |
「普通字」= Text.type=1;等级 = 局内合成等级。有空位时不走本条,直接落子(§7)。
7. 摆放位置
在 AI 己方 CampAreaID 可建造空位上选点。目标按 字典序(先比 1,同分再比 2,再比 3,再比 4):
- 最多武将:落子后场上合成武将(神将)数量尽量多
- Hero.text 字序:与表内左→右序列贴合(成将 / 正序邻接优先;逆序邻接强罚,避免「靖|李」)
- 阵型(路径出口火力 + 覆盖 + 短前长后):能战斗的单位在能打到己方路径的前提下,优先靠出口/攻击点(守门火力);同档再比覆盖格数;再 短射程贴路、长射程可稍离路
- 无攻击功能字(如营):不贴路且 靠入口侧(最里面、远离出口),把出口火力位让给战斗单位;落子扫描序靠后
- 平局:更小
gridIndex
背包落子扫描序同档内 短射程先落(先占出口侧贴路),营等无攻击字最后落。拼装成将时 阵型(含路径出口)优先于步数,同档再短射程武将优先。
7.1 覆盖分与阵型分(已确认)
覆盖分(格子, 单位) = 该单位以该格为锚点、按实战索敌同一套射程判定时,
己方阵营路径上落入射程内的「路径格」数量
路径出口分(格子, 单位) = 射程内己方路径格中,(-距攻击点路径代价) 的最大值
(无 pathfinder 时用 +距己方出怪口格数 近似)
→ 越大越靠近出口 / 攻击点
阵型分(格子, 单位):
- 无攻击功能字(营,isNonCombatWeapon1):
阵型分 = 距路格数 * 10000 - 最近路径格出口分 * 100
→ 越靠里、越靠入口侧越高;不要求覆盖;避免占出口火力位
- 能战斗单位:须覆盖分>0;
frontBias = 2.5 - 射程格数;
阵型分 = 路径出口分 * 100000 + 覆盖分 * 1000 + frontBias * (-距路格数) * 100
→ **路径出口优先**(守门);覆盖与短前长后为次
| 项 | 约定 |
|---|---|
| 度量 | A:射程内的路径格数(不是射程圆面积、不是任意空地格数) |
| 路径出口 | 所有能战斗单位主项:射程内路径格越靠近出口/攻击点越高(守门火力) |
| 射程 | 与实战一致(如 Hero.atk_range / 逻辑层索敌射程;全屏等特殊值按战斗同一规则) |
| 路径格 | 本阵营 CampArea 上怪物可走路径单元格;只计己方路,不计对方路 |
| 锚点 | 落子格中心(组合体等多格占位时与战斗索敌锚点一致) |
| 阵型 | 能战斗:路径出口 + 覆盖 + 短前长后;无攻击(营):最里面且靠入口侧;仅无空位时凑不齐的组合散字可被战斗单位换位让路 |
| 平局 | 阵型分相同则选更小 gridIndex,禁止再引入静默随机除非策划要求 |
铲子选格走 §6.2(aiusechanzi),不用本覆盖/阵型分。本条仅用于「有字要落入空位」时的选点。
禁止落到对方 camp;合法性走与玩家相同的放置校验(按 playerId/camp)。
7.2 合成武将阵型重摆(已确认)
多格武将(Hero.herotype===2 且占多格)成将后,决策链在摆放之后再检:
| 项 | 约定 |
|---|---|
| 时机 | §6.1:摆放之后;例外:可贴路时优先于落 1–3 级单字;可跨多个 1s tick 逐步挪完 |
| 目标 | 贴路边 / 路径出口(怪物出口侧) 阵型分更高的横向 Put 跨度(formationScore:路径出口优先 + 神将贴路加成) |
| 空跨度 | 目标全空且与当前占位不重叠 → 左→右 map→map 挪过去 |
| 换位 | 目标被 单格战斗单位(或营等无攻击字)占用时:先把挡路单位挪到跨度外空位(战斗单位优先仍能打到路的格),再挪神将;若无足够空位且目标整段满占 → 与挡路单位 一一互换 |
| 优先级 | 多格合成神将 > 1–3 级普通单字(有神将时低级单字不主动抢路边;神将重摆可清走路边低级单字) |
| 禁止 | 拆其它多格神将占格;不可落到对方 camp |
| 动作 | 每拍一步排队执行(不经背包) |
字序未成将: 场上/背包已有一套 Hero.text 散字但未贴齐时,走 §6.1「可合成」:一步成将(落空/邻接互换)或 全局拼装(散字凑齐后逐步
map/bag→横向空跨度,阵型/路径出口优先于步数;同一决策拍内连续做完拼装队列,避免悟/空长时间竖拆)。有清桩空位时先挪挡路再落子;无空位时直接与挡路单位互换(含背包字换下占格),禁止因空 Put=0 干等刷新/铲地。组合字落点 优先 横向可拼跨度(composeOrderScore);凑不齐时仍落到空 Put(仅避开逆序邻接强罚格),避免背包积压。组合字(Text.type=2)之间 不能 像普通字那样互叠升级;成将后多余同组字再喂入武将升级。
8. 主动技能与广告模拟
实现顺序: 本节规则为 AI 侧契约;玩家主动技能 / 广告 / 装备掉落 功能尚未完整实现。AI 须 等玩家侧能力就绪后,复用同一套逻辑与数据再同步(禁止 AI 先行猜一套独立规则)。
8.1 主动技能(道具使用)
对应策划「道具使用」:局内主动技能(匹配挂载的 Battlebuff type=1)。
| 项 | 规则 |
|---|---|
| 时机 | CD 结束立即释放(无合法目标 / 条件不足则 保持就绪、不进 CD,下帧再试)。例外:乾坤袋见下行 |
| 乾坤袋 | 不在帧 tick 自动放;仅当决策拍 升级/放字等已穷尽、准备刷新 时:背包仍有可回收多余字 → 收 1 格换桃(低等级优先);场上字永不回收;无多余字则直接刷新 |
| 目标优先 | 默认:合成武将 → 普通字;同档内 高等级优先 |
| 文昌笔目标 | 紫字优先(赌变橙可合成)→ 其余 低等级优先 → 橙字垫底(已可合成,尽量不改);同档再比稳定格序。实现:AiActiveSkillTargetPicker.pickRewrite |
| 来源 | 匹配阶段主动 id 列表(BattleBuffCarryStore) |
| 实现 | AiActiveSkillHost + AiActiveSkillTargetPicker |
| 已接线 | 封神榜、授仙箓、乾坤袋(仅刷新拍:升级/放字穷尽后,背包有多余字才回收,场上永不回收)、文昌笔、攻速符(仅已上场攻击单位)、千里眼(仅已上场远程单位)、万神幡(给玩家出征 Boss,玩家出怪口)、七十二变(roll 后条件不足则等待该技能;非法 kind 重 roll);缚仙网/三昧真火(无格等待);幻魂镜(无神将等待);妙手神偷(没空位/无可偷则等待;仅场上 Put 英雄散字 Combo,不可偷背包;偷完不改格子语义) |
| 施放表现 | AI 主动施放时 复制 ui_otherskill1List 对应槽 icon,飞向单位格 / 出怪口 / 神将中心 / 偷取源格等受体(对齐玩家拖放落点;逻辑仍即时生效,不阻塞 CD) |
8.2 广告触发几率
读对手 Rank.aiusead(万分比)。实现:AiAdHost(经 AiBattleBridge 每帧 tick)。
| 项 | 规则 |
|---|---|
| 概率 | Rank.aiusead(session.rng 掷万分比;失败则本机会不发放) |
| 类型 | 铲子广告、防御广告(门神盾牌);效果对齐玩家,不调用广告 SDK |
| 铲子条件 | 与玩家一致:己方无可放置 Put + 背包有空位且无铲;空位>1 发 2 把否则 1 把 |
| 盾牌条件 | 与玩家一致:战斗中、进攻怪离 AI 终点 ≤ Const23;成功则布置门神陷阱 |
| 投掷上限 | 铲子 Const 22、盾牌 Const 25:每次条件满足后的掷骰均消耗 1 次(成败都计),用尽后本局不再掷 |
| 优先级 | Const23 有威胁时优先盾牌,否则铲子(对齐玩家 btnItem) |
| 状态 | 已实现 |
9. 击杀装备掉落
| 项 | 规则 |
|---|---|
| 归属 | 击杀怪物时按 对手段位 weapondrop / weaponweight 掷骰 |
| 与 AI 决策 | 不进入拖拽状态机;独立掉落/展示/结算管线 |
| 与 §5.6 | 分离:合成武将武器走 Rank.aiweapon(§5.6);本节仅 击杀掉落 |
| 状态 | 等待实现玩家功能后再同步 AI(玩家击杀掉落/展示/结算先落地,AI 复用同一掷骰与展示规则) |
细节若与局外神将武器需求重叠,结算 UI 另文;本文只要求战斗侧按表掷骰且不静默吞掉。
10. 胜负条件
| 项 | 状态 |
|---|---|
| 规则 | 门血 / 波次等细则可继续完善;结算 UI(胜/败)已调 MatchWinStats.record |
| 连胜 | streak 已在胜负结算中更新(§4.5),不再阻塞匹配 |
| 参考 | 方案-出怪逻辑实现.md |
| AI | 与玩家共用 BattleSession 结果,不单独判赢 |
11. 代码组织与冲突隔离(已确认方向)
目标: AI 匹配 + 局内决策 独立目录、独立 PR,他人改玩家 UI / 战斗表现时 尽量不碰 AI 文件,降低合并冲突。
11.1 目录约定(新增代码放此,勿散进 CocosBagBoardView 等)
assets/script/battle/ai/
├── match/ # 开战前匹配(阶段 A)
│ ├── AiMatchProfile.ts # 本局对手档案(只读数据结构)
│ ├── AiMatchService.ts # Const 11/12/14、buff 抽取 → Profile
│ └── AiBattlebuffPicker.ts # §4.4 加权不重复抽取
├── runtime/ # 局内生命周期
│ ├── AiOpponentController.ts # 开战延迟;决策 tick
│ ├── AiBattlePersonality.ts # aiusechanzi / aiusetext 开局抽样
│ ├── AiHeadlessBagHost.ts # playerId=1 无头 BattleBagSession + 字池
│ ├── AiHeroWeaponRegistry.ts # §5.6 本局 heroId→weaponId 存档
│ ├── AiHeroWeaponAssigner.ts # §5.6 aiweapon 品质掷骰 + Weapon 解析
│ ├── AiPassiveCdHost.ts # CD 被动产桃/铲
│ ├── AiActiveSkillHost.ts # §8.1 主动技能
│ └── AiAdHost.ts # §8.2 广告模拟(铲子/门神)
├── decision/ # 纯决策(无 Cocos 节点依赖)
│ ├── AiActionPlanner.ts # §6.1 优先级链
│ ├── AiShovelPlanner.ts # §6.2
│ ├── AiTextPolicyPlanner.ts # §6.3 / §6.3.1
│ ├── AiSwapPlanner.ts # §6.4 互换
│ ├── AiBoardProbe.ts # 探测 / 合成 / §7.2 重摆
│ └── AiPlacementScorer.ts # §7 覆盖分
├── bridge/
│ └── AiBattleBridge.ts # **唯一对外集成层**(见 11.2)
└── types/
└── AiTypes.ts # 动作枚举、Planner 入参/出参
- 禁止 在
ui/、CocosBagBoardView.ts内堆 AI 决策逻辑。 - 禁止 在
assets/mx/写 AI 策划规则(MX 保持通用内核)。 - 复用现有类:
PlayerBagTextPool、BattleBagSession、MergeSession—— 只调用,扩展时优先 包装(AiHeadlessBagHost),少改原文件。
11.2 集成边界(减少与他人冲突的改法)
| 层级 | 他人常改 | AI 侧做法 |
|---|---|---|
| 入口 | GameController / UIBattleGame |
各 最多 2~3 行:开战调 AiMatchService.buildProfile();战斗就绪调 AiBattleBridge.start(profile);结束 stop() |
| 桥接 | — | 全部进 AiBattleBridge:取 BattleRuntime、按 playerId=1 执行 refresh/drop/deploy;武将武器对外 getHeroBattleWeaponId |
| 必须改旧文件时 | DeploySync、MapPlaceValidator、BattleRuntime、TowerLoadoutService |
单独小 PR:playerId / 第二 bag / AI 武器解析挂钩;决策逻辑仍放 ai/ |
| 表现 | TextGrid、战斗特效 |
AI 只发 与玩家相同的 Merge/deploy 事件;网格由现有 presenter 刷新 |
| 匹配 UI | UImatch |
AI 不碰;UI 只读 MatchWinStats / 已有展示;Profile 由 AiMatchService 产出给战斗 |
GameController / UIBattleGame
│ (薄调用)
▼
AiBattleBridge ──► AiOpponentController ──► decision/*
│ │
▼ ▼
BattleRuntime / Merge / DeploySync(playerId=1,共享规则)
11.3 模块职责(与目录对应)
| 模块 | 职责 |
|---|---|
AiMatchService |
Const 11/12/14/15~18/19/20 → AiMatchProfile |
AiBattlebuffPicker |
§4.4 |
AiBattlePersonality |
aiusechanzi / aiusetext |
AiHeadlessBagHost |
playerId=1 字池 + bag |
AiHeroWeaponRegistry / AiHeroWeaponAssigner |
§5.6 合成武将武器:首次掷骰、局内复用 |
AiOpponentController |
开战 2 秒后激活;每 2 秒一步;§6~§7;无动作时刷 1 次 |
AiPlacementScorer |
§7 覆盖分 |
AiBattleBridge |
对外唯一入口;生命周期与 BattleRuntime 对齐 |
11.4 协作约定
- AI 功能 PR 默认只改
assets/script/battle/ai/**+ 必要的 1~2 个挂钩文件。 - 挂钩文件改动须在 PR 说明中 列出路径与行号,便于 review。
RankDataManager.pickOpponentRankId重构 → 可迁入ai/match/或保留 manager 但 实现搬至 AI 模块调用,减少 manager 长期冲突。- 类型定义优先放
ai/types/,避免与他人共改ProjectBattleTypes.ts;若必须扩展,MatchProfile字段 追加可选,不改玩家字段语义。
12. 验收清单
12.0 A0:AI 背包刷新(先过)
AiHeadlessBagHost:playerId=1独立 bag +PlayerBagTextPool.beginBattle(opponentRankId)- 与玩家池 remaining 隔离;玩家点「召唤」不消耗 AI 池
- 开战 首轮自动刷新 填满 5 槽,并按 Const 4/5 扣 AI 槽桃子(不免单)
tryNormalRefresh(或等价)按 playerId=1 扣金,禁止写死localSlot- 字池未就绪 / 耗尽 / 非法 rank →
warn+ 失败,无静默默认字
12.1 其余阶段 A~D
- Const 11 按 星
-5~+4十档匹配;越界权重为 0 - Const 12 / 14 驱动主动 / 被动数量偏移,结果分别 clamp 到 0~2 / 0~6
- 连续 streak + Const 15~18 修正 12/14 权重;跨天不清零;胜/败互斥
- Battlebuff:按
type分池 +functiontype∈[2,27]+ 排除type=3;weight加权不重复;被动对称接线中 - §5.6:合成武将首次
Rank.aiweapon独立加权掷品质(可重复)→Hero.weapon约束下选Weapon.id;同一 heroId 局内 registry 复用(拆开再合不重掷) - 每 1 秒 tick:一步动作或 一次刷新;无桃则等击杀加桃
- 击杀 +1 桃(AI 槽独立);玩家侧击杀加桃 待对齐
- 动作序:可合成/拼装 → 铲 → 升级(包内→包图→图图) → 摆放 →(空不够)§6.4 → 普通字策略 → 散字去重 → 阵型重摆;开局抽
aiusechanzi/aiusetext整局固定 - §6.4:优先级 2级普通 > 橙 > 紫 > 1级普通;橙可挤 1 级普通与紫字(不可挤 2 级普通);紫仅可挤 1 级普通;多目标时先挤低档;非法 quality 须
warn - 落点字典序:武将数 → Hero.text 字序 → 阵型(路径出口火力 + 覆盖 + 短前长后)(营最里且靠入口侧)→ gridIndex;拼装阵型优先于步数;短射程先落、无攻击最后落
- 主动技能:CD 好即放;默认目标优先合成武将→普通字、等级高;文昌笔紫字赌橙→低等级→橙垫底;缚仙网/真火/幻魂镜/神偷已接
Rank.aiusead广告模拟:铲子 / 门神盾牌;只模拟效果、不播广告;Const22/25 限制投掷次数(成败均消耗)- (待玩家功能后同步) 击杀装备掉落:
weapondrop/weaponweight(§9,与 §5.6aiweapon无关) - (待玩家功能后同步) 胜负条件
- 无静默兜底;缺表/空权重
warn
13. 待补项(实现前须对齐)
13.0 需求文档:AI 决策规则 已基本可开发
| 状态 | 内容 |
|---|---|
| 首期切入 | A0 = §5 AI 背包刷新(对齐玩家「召唤」) |
| 已齐 | 匹配、battle 决策、tick/刷新、单字冲级、互换、覆盖分、§5.6 武将武器、§11 独立目录 |
| 仍缺(不阻塞 A0) | 完整匹配 UI、A2 对称化小 PR、下表 |
| 整段后置 | §8/§9 主动/广告/掉落;CD 被动(产桃树/产铲)AI 运行时 |
| 仍须对齐 | 说明 |
|---|---|
| A0 扣金 / 建池 | BattleBagSession 现写死玩家 rank + localSlot;须可按 AI playerId/rankId 调用 |
| 玩家本局 buff 数量读档 | 道具商店需求;AI 只消费个数(A1) |
| 工程对称化小 PR | DeploySync / MapPlaceValidator / slot 金币(A2,与 ai/ 分 PR) |
| 胜负规则 | 定稿后接 streak |
| 玩家侧 §8/§9 | 阶段 D 再同步 AI |
13.1 连胜 / streak(已完成)
| 项 | 说明 |
|---|---|
| 连胜/连败字段 | matchWinStreak / matchLoseStreak;胜负 UI 结算互斥增减;跨天不清零 |
| Const 15~18 | 匹配前修正 12/14 权重(§4.3) |
13.2 阻塞于其它玩家能力(阶段 B / D)
| 项 | 说明 |
|---|---|
| 击杀 +1 桃(玩家侧) | AI 可先按 §5.3 接槽位 gold;玩家管线待对齐 |
| 主动技能 / 广告 / 装备掉落 | §8、§9;玩家实现后 AI 同步 |
| Battlebuff 效果 | 本期只挂载;字池/刷新加成等 效果层待 buff 系统 |
| 玩家本局 buff 数量 | 读当日道具栏的存档结构(依赖道具商店需求) |
13.3 算法与工程细则(已确认 / 已对齐)
13.3.1 总星算法(Const 11 偏移基准)
(算法同前;AI 侧不需要处理总星 UI 展示,匹配只产出 opponentRankId / 内部 opponentStars 供字池与难度。)
minStar = Rank 首档 level[0];maxStar = Rank 末档 level[1](-1 则取表内最大星)
playerStars = getPlayerRankStars()
对每个偏移 d∈[-5..+4]:若 playerStars+d 不在 [minStar,maxStar] → 该档权重=0
随机 d → opponentStars = clamp(playerStars+d, minStar, maxStar) // 绝不越界
opponentRankId = resolveTierByStars(opponentStars).id
代码锚点: RankDataManager(pickOpponentRankId 须改为按 星 十档)。
13.3.2 AI tick(已确认)
| 项 | 规则 |
|---|---|
| 间隔 | 每 1 秒 一次 tick;开战 2 秒 后才激活 |
| 单次 tick | 一步 §6.1 动作;无动作且有桃则 刷 1 次(§5.4) |
| 桃子不够 | 等待击杀加桃,下一 tick 再试,不结束 AI |
13.3.3 一轮动作(已确认)
操作节拍 = 每 1 秒最多一步原子操作(§6.1 优先级链取最高可执行项)
无动作且有桃 → 本拍刷新 1 次;刷新后的落子等下一拍
- 每步一个原子操作(铲 / 合成 / 升级 / 互换 / 落子 / 普通字整理 / 重摆一步)。
- 有钱刷新:按拍逐次,不在同一秒内连刷多轮。
- 不含 §8 主动技能、§8.2 广告。
13.3.4 挤占 / 互换(已确认)
- 占格冲突走 互换:新单位上图格,原单位 回 AI 背包(Merge 互换语义,成对出现)。
- 橙/紫对普通字:见 §6.4。
- 神将:不可被普通字橙/紫挤掉;新神将等级更高 时可与格上 更低等级神将 互换(低的回背包)。
13.3.5 单字冲级
见 §6.3.1:候选 = 战场格子 + 背包;每 1 秒 tick 更新目标;满级后切下一种未满级字(比最高等级,背包与格子均参与)。
13.3.6 被动 buff(已确认)
| 项 | 规则 |
|---|---|
| 匹配 | §4.4 抽到 id 列表 |
| 局内 | 写入 BattleBuffCarryStore;被动对称生效;主动列表可供 §8.1 自动释放 |
| 上限 | Const 19(主动)/ 20(被动) |
13.3.7 匹配 UI
本文不规定匹配页 UI;由匹配 UI 模块自行实现。逻辑层仍产出 MatchProfile(段位、buff ids、策略类型、seed 等)供战斗读取。
13.3.8 Const 11「有效权重为 0」是什么?
含义(极端容错,正常玩不到):
匹配时按 Const 11 随机「对手比玩家多/少几颗星」。不合法档位(会使 玩家星+偏移 低于 minStar 或高于 maxStar)权重 先置 0,再随机;最终 opponentStars 再 clamp 到 [minStar, maxStar],保证不会越界。
若置 0 后 十档权重全为 0(多为 配表错误),则无法抽对手 → warn + 中止匹配。现网均匀权重下 极少触发。
13.3.9 §2 工程对称化验收(阶段 A 必过)
实现 AI 决策前,下列项须 按 playerId=0/1 对称;现网缺口括号标注。
| # | 项 | 验收标准 | 现网参考 |
|---|---|---|---|
| 1 | 放置 camp | MapPlaceValidator(ownerPlayerId):playerId+1 对 CampAreaID(0→1 / 1→2);AI bag 传 ownerPlayerId=1,不再覆盖门禁 |
出怪口反查会错;须按 playerId 而非写死本机 |
| 2 | DeploySync | 监听 指定 playerId 的 bagSession;reconcile 时 getPlayer(id) + deploy({ playerId }) |
已支持 bind(..., playerId);AI 走 DeploySync(1) |
| 3 | 无头 bag | playerId=1 有独立 BattleBagSession + PlayerBagTextPool(opponentRankId);Player.bagSession 远端非 null 或等价服务 |
远端 bagSession null |
| 4 | 桃子 | 击杀 +1 写入 对应 slot.gold;刷新扣 对应 slot;UI 只显示本机 | BattleRuntime 偏 localGold |
| 5 | 墙血 / 胜负 | 怪到门扣 对应 owner 的 wallHp;双槽独立(阶段 B 与 §10 一并验收) |
部分 仅 localSlot |
| 6 | 波次金币 | 若有波次奖励,按 camp 归属入账 | 待查 |
| 7 | 塔归属 | AI ownerId=1 打攻 AI 的车道怪;落子仅 CampAreaID=2 |
enemy(2) put=0 则地图未标敌方放置区 |
| 8 | 表现 | AI 阵营 TextGrid 同步;可无底部 5 格 UI |
仅本机 CocosBagBoardView |
| 9 | AI 武将武器 | 合成首次分配;registry 复用;TowerLoadout 读 AI 武器 id |
现网仅 gg.equip |
| 10 | 快照 | 若做断线重连,须含 AI bag/金/部署/武器 registry;本期可 声明不做 | BattleSnapshot 偏单 bag |
通过标准: 阶段 A 至少 #1~#4、#7~#9 可手工验证(AI 能 Refresh、Place、Deploy 到 AI camp 且 ECS 塔战斗、合成武将有稳定武器)。
14. 相关代码锚点(只读参考)
| 路径 | 说明 |
|---|---|
assets/script/battle/ai/ |
新建:匹配 + 决策 + 桥接(§11,独立 PR) |
assets/script/battle/bag/PlayerBagTextPool.ts |
字池(AI 复用,换 rankId) |
assets/script/manager/RankDataManager.ts |
总星 API;pickOpponentRankId 待改十档 |
assets/script/battle/runtime/DeploySync.ts |
集成 PR:支持 playerId=1 |
assets/script/battle/bag/MapPlaceValidator.ts |
集成 PR:按 player camp 校验 |
assets/mx/module/battle/player/PvPBattleMode 等 |
双槽与 camp 策略 |