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(); 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; }