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.
135 lines
5.2 KiB
135 lines
5.2 KiB
import { aiOp } from '../runtime/AiLog';
|
|
import { AiActionKind } from '../types/AiTypes';
|
|
import { AiShovelPolicyType } from '../runtime/AiBattlePersonality';
|
|
import { AiBoardProbe } from './AiBoardProbe';
|
|
import type { MapGrid } from '../../map/legacy/MapGrid';
|
|
import { MapCellKind } from 'db://assets/mx/module/merge';
|
|
import type { AiPlannerContext, AiPlannedAction } from '../types/AiTypes';
|
|
|
|
/** 需求 6.2 铲子使用策略 */
|
|
export class AiShovelPlanner {
|
|
private readonly _probe = new AiBoardProbe();
|
|
|
|
/**
|
|
* 尝试规划一步铲子动作。
|
|
* @param ctx 决策上下文
|
|
*/
|
|
plan(ctx: AiPlannerContext): AiPlannedAction | null {
|
|
const slotIndex = this._probe.findShovelSlotIndex(ctx.bagSession);
|
|
if (slotIndex < 0) return null;
|
|
const digs = this._probe.listEmptyDigGrids(ctx.bagSession, ctx.map);
|
|
if (digs.length <= 0) return null;
|
|
const pick = this._pickDig(ctx, digs);
|
|
if (!pick) return null;
|
|
aiOp(
|
|
`铲子规划 policy=${ctx.personality.shovelPolicy}`
|
|
+ ` digCandidates=${digs.length} pick=map[${pick.Index}] rc=(${pick.Row},${pick.Col})`
|
|
+ ` walk=${pick.isCanWalk} dig=${pick.isCanBeDig}`
|
|
+ ` score=${this._type3Score(ctx, pick)}`,
|
|
);
|
|
return {
|
|
kind: AiActionKind.Shovel,
|
|
merge: {
|
|
source: { kind: 'bag', slotIndex },
|
|
target: { kind: 'map', gridIndex: pick.Index },
|
|
},
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 按本局铲子类型选挖格。
|
|
* @param ctx 上下文
|
|
* @param digs 候选挖格
|
|
*/
|
|
private _pickDig(ctx: AiPlannerContext, digs: MapGrid[]): MapGrid | null {
|
|
const policy = ctx.personality.shovelPolicy;
|
|
if (policy === AiShovelPolicyType.Type1) {
|
|
let x = (Math.floor(Number(ctx.profile.seed) || 0) >>> 0) || 1;
|
|
x ^= x << 13;
|
|
x ^= x >>> 17;
|
|
x ^= x << 5;
|
|
const idx = (x >>> 0) % digs.length;
|
|
return digs[idx] ?? null;
|
|
}
|
|
if (policy === AiShovelPolicyType.Type2) {
|
|
// 先扩已开岛 / 纯 Dig,再贴邻
|
|
let best: MapGrid | null = null;
|
|
let bestScore = -1e99;
|
|
for (let i = 0; i < digs.length; i++) {
|
|
const dig = digs[i];
|
|
let score = 0;
|
|
if (this._hasOpenedNeighbor(ctx, dig)) score += 100000;
|
|
score += this._countWalkNeighbors(ctx, dig) * 1000;
|
|
if (score > bestScore) {
|
|
bestScore = score;
|
|
best = dig;
|
|
}
|
|
}
|
|
return best;
|
|
}
|
|
// Type3 贴路:可走邻接中优先「靠已开 Put / 靠中部」,禁止总挖地图顶边 Dig 条
|
|
return this._pickType3AlongPath(ctx, digs);
|
|
}
|
|
|
|
/**
|
|
* 贴路选挖:优先贴已开 Put;其次可走邻接候选;避免固定扫到顶边 Dig。
|
|
* @param ctx 上下文
|
|
* @param digs 全部空挖格
|
|
*/
|
|
private _pickType3AlongPath(ctx: AiPlannerContext, digs: MapGrid[]): MapGrid | null {
|
|
let best: MapGrid | null = null;
|
|
let bestScore = -1e99;
|
|
for (let i = 0; i < digs.length; i++) {
|
|
const dig = digs[i];
|
|
const score = this._type3Score(ctx, dig);
|
|
if (score > bestScore) {
|
|
bestScore = score;
|
|
best = dig;
|
|
}
|
|
}
|
|
return best;
|
|
}
|
|
|
|
/**
|
|
* Type3 挖格分:贴已开 Put ≫ 可走邻接 ≫ 靠河;边条 Dig 降权。
|
|
* dig+putText 双标已按可放置优先剔除,不会进入候选。
|
|
* @param ctx 上下文
|
|
* @param dig 候选
|
|
*/
|
|
private _type3Score(ctx: AiPlannerContext, dig: MapGrid): number {
|
|
let score = 0;
|
|
if (this._hasOpenedNeighbor(ctx, dig)) score += 100000;
|
|
score += this._countWalkNeighbors(ctx, dig) * 1000;
|
|
// 顶/底边 Dig 降权,避免连续铲开整条边 Dig 后把塔摆到地图外缘
|
|
const maxRow = Math.max(0, (ctx.map.mapRows | 0) - 1);
|
|
if (dig.Row <= 0 || dig.Row >= maxRow) score -= 50000;
|
|
score += dig.Row * 10;
|
|
return score;
|
|
}
|
|
|
|
private _hasOpenedNeighbor(ctx: AiPlannerContext, dig: MapGrid): boolean {
|
|
const grids = ctx.map.mapGrids;
|
|
if (!Array.isArray(grids)) return false;
|
|
for (let i = 0; i < grids.length; i++) {
|
|
const mg = grids[i];
|
|
if (!mg?.node?.isValid) continue;
|
|
if (Math.abs(mg.Row - dig.Row) + Math.abs(mg.Col - dig.Col) !== 1) continue;
|
|
if (ctx.bagSession.merge.map.getCellKind(mg.Index) === MapCellKind.Put) return true;
|
|
if (ctx.bagSession.merge.map.hasTile(mg.Index)) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private _countWalkNeighbors(ctx: AiPlannerContext, dig: MapGrid): number {
|
|
const grids = ctx.map.mapGrids;
|
|
if (!Array.isArray(grids)) return 0;
|
|
let n = 0;
|
|
for (let i = 0; i < grids.length; i++) {
|
|
const mg = grids[i];
|
|
if (!mg?.node?.isValid) continue;
|
|
if (Math.abs(mg.Row - dig.Row) + Math.abs(mg.Col - dig.Col) !== 1) continue;
|
|
if (mg.isCanWalk) n++;
|
|
}
|
|
return n;
|
|
}
|
|
}
|
|
|