import type { AiPlannerContext, AiPlannedAction } from '../types/AiTypes'; import { AiActionKind } from '../types/AiTypes'; import { AiPlacementScorer } from './AiPlacementScorer'; import { AiBoardProbe } from './AiBoardProbe'; import { aiOp } from '../runtime/AiLog'; /** * AI 决策:解锁 → 产卡使用 → 五行合成 → 上阵。 */ export class AiActionPlanner { private readonly _probe = new AiBoardProbe(); private readonly _place = new AiPlacementScorer(); /** * AI 空 Put 格数量(供刷新门禁)。 * @param ctx 决策上下文 */ countEmptyTextPut(ctx: AiPlannerContext): number { return this._probe.countEmptyTextPut(ctx.bagSession, ctx.map); } /** * 规划下一步原子动作;无可执行时返回 null。 * @param ctx 决策上下文 */ planNext(ctx: AiPlannerContext): AiPlannedAction | null { const emptyPut = this._probe.countEmptyTextPut(ctx.bagSession, ctx.map); const dig = this._probe.listEmptyDigGrids(ctx.bagSession, ctx.map).length; const unlock = this._probe.findUnlockAreaAction(ctx.bagSession, ctx.map); if (unlock) { return this._logAction('解锁地块', { kind: AiActionKind.Place, merge: unlock }, emptyPut, dig); } const card = this._probe.findBattleCardAction(ctx.bagSession, ctx.map); if (card) { // Place:勿用 Upgrade,避免 onBagMergedAt 误推进待开启地块 return this._logAction('产卡使用', { kind: AiActionKind.Place, merge: card }, emptyPut, dig); } const upgrade = this._probe.findUpgradeAction(ctx.bagSession, ctx.map); if (upgrade) { return this._logAction('数字合成', { kind: AiActionKind.Upgrade, merge: upgrade }, emptyPut, dig); } const place = this._place.plan(ctx); if (place) return this._logAction('上阵', place, emptyPut, dig); aiOp(`本拍无动作 emptyPut=${emptyPut} dig=${dig}`); return null; } /** * 仅规划落子(同拍回填空位)。 * @param ctx 决策上下文 */ planPlaceOnly(ctx: AiPlannerContext): AiPlannedAction | null { const place = this._place.plan(ctx); if (!place) return null; const emptyPut = this._probe.countEmptyTextPut(ctx.bagSession, ctx.map); return this._logAction('摆放(同拍回填)', place, emptyPut, 0); } /** * 打印并返回规划动作。 * @param label 动作中文名 * @param action 动作 * @param emptyPut 空 Put * @param dig 可铲数 */ private _logAction(label: string, action: AiPlannedAction, emptyPut: number, dig: number): AiPlannedAction { const m = action.merge; let detail = `kind=${action.kind}`; if (m) { const src = m.source.kind === 'bag' ? `bag[${m.source.slotIndex}]` : `map[${m.source.gridIndex}]`; const dst = m.target.kind === 'bag' ? `bag[${m.target.slotIndex}]` : `map[${m.target.gridIndex}]`; detail += ` ${src}->${dst}`; } aiOp(`决策 ${label} | ${detail} | emptyPut=${emptyPut} dig=${dig}`); return action; } /** * @deprecated 使用 planNext * @param ctx 决策上下文 */ planRound(ctx: AiPlannerContext): AiPlannedAction[] { const action = this.planNext(ctx); return action ? [action] : []; } }