You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
AdLike2048/docs/需求-AI决策.md

49 KiB

需求:匹配与 AI 决策

状态:正式需求(匹配 + A0 背包刷新已接线;决策细则继续完善)
日期:2026-09-01(实现入口更新:2026-09-03)
来源:策划脑图「西游对战 ai」+ 架构讨论确认
关联:需求-五行合成.md、需求-新项目概述.md、需求-背包刷新.md、方案-出怪逻辑实现.md、开发规范.md

局内合成与上阵(Slice5): 仅 五行数字合成 + 单格上阵 + 开地(铲) + 互换。多字拼将、神将重摆、Hero.text 字序等组合玩法 已废弃(见 需求-五行合成.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 建池;字池不含 type=2 组合字
需求-新项目概述.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 对手段位(按星)

  1. 取玩家 总星 RankDataManager.getPlayerRankStars()(存档 GameDoc.rankStars,详见 §13.3.1)。
  2. 读 Rank 表得到 全局最小星 minStar(首档 level[0])、全局最大星 maxStar(末档 level[1],-1 表示无上限时取末档 level[0] 或表内最大星)。
  3. 复制 Const 11 十档权重;对每一档偏移 d:若 玩家星 + d < minStar 或 > maxStar,该档 权重视为 0(不参与随机)。
  4. 在剩余权重中加权随机偏移 d ∈ {−5…+4}。
  5. opponentStars = clamp(玩家星 + d, minStar, maxStar)(不会低于最小星、不会超过最高星)→ opponentRankId = resolveTierByStars(opponentStars).id。

实现备注:现网 pickOpponentRankId 仍按 段位 id 三档读 Const 11,须改为 总星十档偏移(§13.3.1)。

4.2 对手 buff 数量(AI 带入数量)

  1. 取玩家本局带入的主动数、被动数(存档/当日道具栏,与道具商店对齐)。
  2. 复制 Const 12 / 14 各档权重。
  3. 按连胜/连败用 Const 15~18 修正权重(见 §4.3)。
  4. 分别加权随机数量偏移; AI主动数 = clamp(玩家主动数 + 偏移, 0, Const19),
    AI被动数 = clamp(玩家被动数 + 偏移, 0, Const20)。
  5. 按 §4.4 从 Battlebuff 表加权抽取 不重复 的 buff id 列表,写入 MatchProfile。
  6. 开战: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,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 用于 局内该 heroId 首次上场(deploy) 时的武器品质掷骰;weapondrop / weaponweight 仍走 §9 击杀掉落管线(阶段 D)。不含多字拼将成将。

读 对手段位 Rank.aiweapon(5 个数,对应 白 / 绿 / 蓝 / 紫 / 橙,即 Weapon.quality 0~4)。

AI 首次 deploy 单格 heroId
    → Hero.weapon 限定可穿武器类型(同 Weapon.type,与 gg.equip.canWearType 一致)
    → Rank.aiweapon 加权随机品质 q∈{0..4}
    → 在 Weapon 表中选 type=Hero.weapon 且 quality=q 的行作为装备
    → 写入本局 registry[heroId]=weaponId(整局只掷一次)
再次合成同一 heroId(拆开又合)→ 直接读 registry,不再掷骰
项 规则
触发 该 heroId 第一次进入 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
    DeployEvt[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 橙)。空格取舍时见 §6.4。

6.1 动作优先级(高 → 低,Slice5)

  1. 五行数字可合成:背包内 / 背包↔地图 / 地图内 同级同规则 合成升级(不含多字拼将)
  2. 优先使用铲子(背包有铲且有可铲格时,按 §6.2 本局类型 开地)
  3. 空位摆放单格单位(§7;背包有字且有空 Put 时优先落子)
  4. 空位不足:按 §6.4 品质 / 等级 互换 腾位
  5. 普通字策略(§6.3 本局 aiusetext 类型)
  6. 散字去重/替换(仅无空位时):同 Text.id 多余份收回或换格
  7. 若本拍无可做动作且有桃,按 §5.4 刷 1 次;无桃则等下一 tick(§5.5)

说明:主路径 合成升级 → 开地 → 摆放 → 互换;不再规划拼将、神将重摆、Hero.text 字序。

同优先级内扫描顺序(背包从左到右、地图行列)实现时固定一种并保持稳定,避免帧间抖动。

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 里 连续多步操作 共用同一个目标字。

已确认规则:

  1. 每个 tick 开始时 按下列规则 选/更新 目标字(可与上一 tick 相同,也可换)。
  2. 该 tick 内所有 §6.3 操作 共用 此目标,直到下一秒下一个 tick。
  3. 若目标字 已满级 → 从 AI 己方战场格子 + 背包 中的未满级普通字里,改选「当前局内等级最高」的下一种作为新目标;若 均无 未满级普通字,则本 tick 退化为类型 1 行为。

怎么选目标字:

  1. 扫描 AI 战场格子上 + 背包里 的全部 普通字(Text.type=1)。同一 Text.id 多处存在时,以 该 id 当前最高局内等级 参与比较(背包里也可能有高级字)。
  2. 只保留 局内等级 < level_limit 的字种。
  3. 取其中 最高局内等级 的字 id 作为目标;并列 → 若在格上则比覆盖分 → 固定扫描序。

选定目标字之后:

动作 说明
保留 该字 等级最高的那一颗(格上或背包)尽量留在/放到地图好位置
合成 优先 同 id 低级副本 合成升级(背包 ↔ 格子均可)
腾位 优先动 其它字 或 同字低级副本,不动该 id 最高等级那颗
落子 若最高等级仅在背包,优先 落到格子 以提升覆盖(§7)

举例: 格上 3 级「刀」将满级;背包里有 2 级「枪」→ 刀满级后下一目标可选「枪」。若格上 3 级刀、背包 4 级「棍」,则 tick 初就可能以「棍」为目标并优先落子到格上。

6.4 空位不足时的 互换

仅当 己方可建造空位(Put)不足 时启用。本质是 格子上的单位互换(被换下的进背包),不是凭空覆盖。 背包有铲且仍有 Dig 时由 §6.1 先铲(铲子优先于互换),不走到本条;无铲时允许互换,避免 Dig 未开完、又刷不起背包时长期空转。
编辑防御:地图格若同时勾选 isCanPutText + isCanBeDig,逻辑层 可放置优先(按 Put 入场,不当 Dig);纯 Dig(仅勾挖掘)仍须铲子。

占格优先级(高 → 低):

高局内等级 / 高品质  >  低等级 / 低品质单格单位
规则 说明
等级 局内五行合成等级更高者优先保留格位
品质 同等级时 Text.quality 更高者优先(橙 > 紫 > … > 白)
被换下单位 进入 AI 背包(与 Merge 互换规则一致;互换成对出现,不应出现背包满无法接收)

有空位时不走本条,直接落子(§7)。不含多格占格、拼将散字特例。


7. 摆放位置

在 AI 己方 CampAreaID 可建造空位上选点。目标按 字典序(先比 1,同分再比 2,再比 3):

  1. 阵型(路径出口火力 + 覆盖 + 短前长后):能战斗的单格单位在能打到己方路径的前提下,优先靠出口/攻击点(守门火力);同档再比覆盖格数;再 短射程贴路、长射程可稍离路
  2. 无攻击功能字(如营):不贴路且 靠入口侧(最里面、远离出口),把出口火力位让给战斗单位;落子扫描序靠后
  3. 平局:更小 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 多格神将阵型重摆(已废弃)

已废弃(Slice5):战斗仅单格上阵,不再有多格神将重摆或 Hero.text 拼装落点。见 需求-五行合成.md。


8. 主动技能与广告模拟

实现顺序: 本节规则为 AI 侧契约;玩家主动技能 / 广告 / 装备掉落 功能尚未完整实现。AI 须 等玩家侧能力就绪后,复用同一套逻辑与数据再同步(禁止 AI 先行猜一套独立规则)。

8.1 主动技能(道具使用)

对应策划「道具使用」:局内主动技能(匹配挂载的 Battlebuff type=1)。

项 规则
时机 CD 结束立即释放(无合法目标 / 条件不足则 保持就绪、不进 CD,下帧再试)。例外:乾坤袋见下行
乾坤袋 不在帧 tick 自动放;仅当决策拍 升级/放字等已穷尽、准备刷新 时:背包仍有可回收多余字 → 收 1 格换桃(低等级优先);场上字永不回收;无多余字则直接刷新
目标优先 默认:已上场战斗单位 → 背包字;同档内 高等级优先
文昌笔目标 高品质优先 → 其余 低等级优先;同档再比稳定格序。实现:AiActiveSkillTargetPicker.pickRewrite
来源 匹配阶段主动 id 列表(BattleBuffCarryStore)
实现 AiActiveSkillHost + AiActiveSkillTargetPicker
已接线 封神榜、授仙箓、乾坤袋(仅刷新拍:升级/放字穷尽后,背包有多余字才回收,场上永不回收)、文昌笔、攻速符(仅已上场攻击单位)、千里眼(仅已上场远程单位)、万神幡(给玩家出征 Boss,玩家出怪口)、七十二变(roll 后条件不足则等待该技能;非法 kind 重 roll);缚仙网/三昧真火(无格等待);幻魂镜(无合法目标则等待);妙手神偷(没空位/无可偷则等待;仅场上 Put 单格字,不可偷背包;偷完不改格子语义)
施放表现 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         # 探测 / 五行合成 / 落子
│   └── 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 协作约定

  1. AI 功能 PR 默认只改 assets/script/battle/ai/** + 必要的 1~2 个挂钩文件。
  2. 挂钩文件改动须在 PR 说明中 列出路径与行号,便于 review。
  3. RankDataManager.pickOpponentRankId 重构 → 可迁入 ai/match/ 或保留 manager 但 实现搬至 AI 模块调用,减少 manager 长期冲突。
  4. 类型定义优先放 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:空位不足时按局内等级 / Text.quality 互换腾位;非法 quality 须 warn
  • 落点字典序:阵型(路径出口火力 + 覆盖 + 短前长后)(营最里且靠入口侧)→ gridIndex;短射程先落、无攻击最后落
  • 主动技能:CD 好即放;默认目标优先已上场单位、等级高;文昌笔高品质→低等级;缚仙网/真火/幻魂镜/神偷已接
  • Rank.aiusead 广告模拟:铲子 / 门神盾牌;只模拟效果、不播广告;Const22/25 限制投掷次数(成败均消耗)
  • (待玩家功能后同步) 击杀装备掉落:weapondrop / weaponweight(§9,与 §5.6 aiweapon 无关)
  • (待玩家功能后同步) 胜负条件
  • 无静默兜底;缺表/空权重 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。
  • 互换仅针对 单格 占格单位(见 §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 策略