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.
95 lines
2.9 KiB
95 lines
2.9 KiB
|
3 weeks ago
|
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;
|
||
|
|
}
|