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/assets/script/battle/ai/decision/AiActionPlanner.ts

95 lines
3.6 KiB

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}`;
}
if (label === '解锁地块' || label === '产卡使用' || label === '数字合成' || label === '上阵') {
console.warn('[AI操作]', `决策 ${label} | ${detail} | emptyPut=${emptyPut} dig=${dig}`);
} else {
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] : [];
}
}