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/mx/module/battle/battlefield/PatrolLoopBuilder.ts

94 lines
2.9 KiB

import type { Cell } from './Cell';
import { cellKey } from './Cell';
import type { GridMap } from './GridMap';
import { createMembershipSet, membershipAdd } from '../../../tools/MembershipSet';
const ORTH: readonly { dr: number; dc: number }[] = [
{ dr: -1, dc: 0 },
{ dr: 1, dc: 0 },
{ dr: 0, dc: -1 },
{ dr: 0, dc: 1 },
];
/**
* 从当前可走格构建唯一正交闭环(外围环走)。
* 要求:可走格构成简单环(每格恰好 2 个正交可走邻居),并按顺时针定向。
* @returns 有序闭环(不含重复起点);无法建环返回 null
*/
export function buildPatrolLoopFromWalkable(grid: GridMap): Cell[] | null {
const walkable: Cell[] = [];
for (let row = 0; row < grid.rows; row++) {
for (let col = 0; col < grid.cols; col++) {
const cell = { row, col };
if (grid.isWalkable(cell)) walkable.push(cell);
}
}
if (walkable.length < 4) return null;
const walkSet = createMembershipSet<string>();
for (const c of walkable) membershipAdd(walkSet, cellKey(c));
const neighbors = (c: Cell): Cell[] => {
const out: Cell[] = [];
for (const d of ORTH) {
const nb = { row: c.row + d.dr, col: c.col + d.dc };
if (!grid.inBounds(nb)) continue;
if (!walkSet.has(cellKey(nb))) continue;
out.push(nb);
}
return out;
};
for (const c of walkable) {
if (neighbors(c).length !== 2) return null;
}
let start = walkable[0];
for (const c of walkable) {
if (c.row < start.row || (c.row === start.row && c.col < start.col)) {
start = c;
}
}
const startNbs = neighbors(start);
// 顺时针:外环左上角优先向右,其次向下
let first: Cell | null = null;
for (const prefer of [
{ row: start.row, col: start.col + 1 },
{ row: start.row + 1, col: start.col },
{ row: start.row, col: start.col - 1 },
{ row: start.row - 1, col: start.col },
]) {
if (startNbs.some((n) => n.row === prefer.row && n.col === prefer.col)) {
first = prefer;
break;
}
}
if (!first) return null;
const loop: Cell[] = [{ ...start }];
let prev = start;
let cur = first;
const maxStep = walkable.length + 2;
for (let step = 0; step < maxStep; step++) {
loop.push({ ...cur });
if (cur.row === start.row && cur.col === start.col) {
// 回到起点:去掉末尾重复起点
loop.pop();
break;
}
const nbs = neighbors(cur);
let next: Cell | null = null;
for (const n of nbs) {
if (n.row === prev.row && n.col === prev.col) continue;
next = n;
break;
}
if (!next) return null;
prev = cur;
cur = next;
}
if (loop.length !== walkable.length) return null;
return loop;
}