From cbcaec83dccb2919360c28f7ea65d82b5d024cb5 Mon Sep 17 00:00:00 2001 From: unanmed <1319491857@qq.com> Date: Wed, 9 Sep 2026 17:44:31 +0800 Subject: [PATCH] refactor(02-02): comply with updated style rules, extract finder and drop temp function consts --- .../data-common/src/common/mover.test.ts | 4 +- packages-user/data-system/src/path/finder.ts | 195 +++++++++++++++++ .../data-system/src/path/graph.test.ts | 11 +- packages-user/data-system/src/path/graph.ts | 7 +- packages-user/data-system/src/path/index.ts | 1 + packages-user/data-system/src/path/system.ts | 201 +----------------- 6 files changed, 212 insertions(+), 207 deletions(-) create mode 100644 packages-user/data-system/src/path/finder.ts diff --git a/packages-user/data-common/src/common/mover.test.ts b/packages-user/data-common/src/common/mover.test.ts index 37bfb0a..c396286 100644 --- a/packages-user/data-common/src/common/mover.test.ts +++ b/packages-user/data-common/src/common/mover.test.ts @@ -1,9 +1,7 @@ // 验证 L0 ObjectMover 单步移动后的坐标回写行为(正交 / 斜向 / 传送步),为 mover.ts:651 条件缺陷的修复铺设回归用例 import { beforeAll, describe, expect, it, vi } from 'vitest'; import { type ITileLocator } from '@motajs/common'; -import { - FaceDirection -} from '@user/data-common'; +import { FaceDirection } from '@user/data-common'; import { type IObjectMovable, type ObjectMoveStep, diff --git a/packages-user/data-system/src/path/finder.ts b/packages-user/data-system/src/path/finder.ts new file mode 100644 index 0000000..c11bc94 --- /dev/null +++ b/packages-user/data-system/src/path/finder.ts @@ -0,0 +1,195 @@ +import { InternalDirectionGroup, ITileLocator, logger } from '@motajs/common'; +import { + ILayerLocation, + IMapLayer, + IMapState, + IPassPredicate, + IStateBase +} from '@user/data-base'; +import { isNil } from 'lodash-es'; +import { IPathGraph, PathfindingGraphBuilder } from './graph'; +import { IPathfinder, IPathfindingStep, PathCostFunction } from './types'; + +//#region 寻路求解器 + +/** + * 寻路求解器,在动态构建的有向图上执行最小损失搜索。 + * 损失函数与通行性谓词均为可注入槽位,未注入损失函数时每格损失 1, + * 未注入谓词时所有边均不可通行 + */ +export class PathfindingFinder implements IPathfinder { + /** 当前对象对应的数据层对象 */ + readonly state: IStateBase; + + /** 绑定的地图状态对象,用于解析楼层 id */ + private maps: IMapState | null = null; + /** 绑定的地图图层,图节点来源 */ + private layer: IMapLayer | null = null; + /** 注入的通行性谓词,用于判定边可行性与终端节点 */ + private predicate: IPassPredicate | null = null; + /** 注入的损失函数,未注入时每格损失 1 */ + private cost: PathCostFunction | null = null; + /** 邻域方向组别,默认四正交方向 */ + private group: number = InternalDirectionGroup.Dir4; + + constructor(state: IStateBase) { + this.state = state; + } + + /** + * 绑定寻路所用的地图状态对象 + * @param maps 地图状态对象,传入 `null` 解绑 + */ + useMapState(maps: IMapState | null): void { + this.maps = maps; + } + + /** + * 绑定构建有向图所用的地图图层 + * @param layer 地图图层对象,传入 `null` 解绑 + */ + useMapLayer(layer: IMapLayer | null): void { + this.layer = layer; + } + + /** + * 设置自定义损失函数 + * @param cost 损失函数,传入 `null` 恢复默认 + */ + useCostFunction(cost: PathCostFunction | null): void { + this.cost = cost; + } + + /** + * 设置通行性谓词 + * @param predicate 通行性谓词,传入 `null` 恢复默认 + */ + usePassPredicate(predicate: IPassPredicate | null): void { + this.predicate = predicate; + } + + /** + * 设置寻路使用的朝向组 + * @param group 朝向组 + */ + useDirGroup(group: number): void { + this.group = group; + } + + /** + * 在当前绑定状态下执行寻路,返回损失最小的步骤序列。 + * 地图状态或图层未绑定、坐标越界等非法输入下告警并返回空数组; + * 目标不可达时同样返回空数组 + * @param start 寻路起始位置 + * @param target 寻路目标位置 + */ + find(start: ITileLocator, target: ITileLocator): IPathfindingStep[] { + const maps = this.maps; + const layer = this.layer; + if (isNil(maps) || isNil(layer)) { + logger.warn(173); + return []; + } + if ( + !layer.inMap(start.x, start.y) || + !layer.inMap(target.x, target.y) + ) { + logger.warn(173); + return []; + } + + // 数据端状态可变,每次寻路动态构建图,不做缓存 + const builder = new PathfindingGraphBuilder(); + builder.useMapState(maps); + builder.useMapLayer(layer); + builder.usePassPredicate(this.predicate); + builder.useDirGroup(this.group); + const graph = builder.build(); + return this.search(graph, start, target); + } + + /** + * 在有向图上执行 Dijkstra 最小损失搜索, + * 终端节点仅可作为路径终点,不可作为中间节点 + * @param graph 寻路有向图 + * @param start 寻路起始位置 + * @param target 寻路目标位置 + */ + private search( + graph: IPathGraph, + start: ITileLocator, + target: ITileLocator + ): IPathfindingStep[] { + const startIndex = start.y * graph.width + start.x; + const targetIndex = target.y * graph.width + target.x; + if (startIndex === targetIndex) return []; + if (!graph.nodes.has(startIndex) || !graph.nodes.has(targetIndex)) { + return []; + } + + const dist: Map = new Map(); + const prev: Map = new Map(); + const visited: Set = new Set(); + dist.set(startIndex, 0); + + while (true) { + let currIndex = -1; + let currDist = Infinity; + for (const [index, value] of dist) { + if (!visited.has(index) && value < currDist) { + currIndex = index; + currDist = value; + } + } + if (currIndex === -1) return []; + if (currIndex === targetIndex) break; + visited.add(currIndex); + const node = graph.nodes.get(currIndex); + if (!node || node.terminal) continue; + for (const edge of node.edges) { + if (visited.has(edge.to)) continue; + const next = graph.nodes.get(edge.to); + if (!next) continue; + if (next.terminal && edge.to !== targetIndex) continue; + const total = currDist + this.getNodeCost(next.block); + const known = dist.get(edge.to); + if (isNil(known) || total < known) { + dist.set(edge.to, total); + prev.set(edge.to, { + dir: edge.dir, + from: { x: node.x, y: node.y }, + to: { x: next.x, y: next.y } + }); + } + } + } + + const steps: IPathfindingStep[] = []; + let curr = targetIndex; + while (curr !== startIndex) { + const step = prev.get(curr); + if (!step) return []; + steps.push(step); + curr = step.from.y * graph.width + step.from.x; + } + steps.reverse(); + return steps; + } + + /** + * 获取进入指定位置节点的损失,非有限数或负数时 + * 告警并按默认损失 1 处理,保证搜索的非负权不变式 + * @param block 位置信息 + */ + private getNodeCost(block: ILayerLocation): number { + if (!this.cost) return 1; + const value = this.cost(block); + if (!Number.isFinite(value) || value < 0) { + logger.warn(174); + return 1; + } + return value; + } +} + +//#endregion diff --git a/packages-user/data-system/src/path/graph.test.ts b/packages-user/data-system/src/path/graph.test.ts index 037a768..d3305df 100644 --- a/packages-user/data-system/src/path/graph.test.ts +++ b/packages-user/data-system/src/path/graph.test.ts @@ -4,11 +4,13 @@ import { FaceDirection } from '@user/data-common'; import { type IDataCommon, type IFaceHandler, - type IGameMap, - type IPassCheckHandler, - type IPassPredicate, type ITileStore } from '@user/data-common'; +import { + type IGameMap, + type IPassCheckHandler, + type IPassPredicate +} from '@user/data-base'; import { InternalDirectionGroup } from '@motajs/common'; vi.hoisted(() => { @@ -87,7 +89,8 @@ const ONEWAY_TILE: TestTileDefinition = { num: 3, id: 'oneway', outPass: 0b0010, - inPass: 0 + inPass: 0, + eventPass: true }; /** 汇入图块:仅可从左方向进入,不可离开 */ diff --git a/packages-user/data-system/src/path/graph.ts b/packages-user/data-system/src/path/graph.ts index ed5021c..a6b2361 100644 --- a/packages-user/data-system/src/path/graph.ts +++ b/packages-user/data-system/src/path/graph.ts @@ -194,9 +194,10 @@ export class PathfindingGraphBuilder implements IPathfindingGraphBuilder { floorId, state }; - const predicate = this.predicate; - if (isNil(predicate) || !predicate.canPass(handler)) continue; - if (predicate.shouldHit(handler)) { + if (isNil(this.predicate) || !this.predicate.canPass(handler)) { + continue; + } + if (this.predicate.shouldHit(handler)) { terminals.add(ny * width + nx); } edges.push({ dir, to: ny * width + nx }); diff --git a/packages-user/data-system/src/path/index.ts b/packages-user/data-system/src/path/index.ts index 5b6e9dd..6f21121 100644 --- a/packages-user/data-system/src/path/index.ts +++ b/packages-user/data-system/src/path/index.ts @@ -1,3 +1,4 @@ +export * from './finder'; export * from './graph'; export * from './system'; export * from './types'; diff --git a/packages-user/data-system/src/path/system.ts b/packages-user/data-system/src/path/system.ts index 00f23dd..87f56f9 100644 --- a/packages-user/data-system/src/path/system.ts +++ b/packages-user/data-system/src/path/system.ts @@ -1,20 +1,13 @@ -import { InternalDirectionGroup, ITileLocator, logger } from '@motajs/common'; +import { ITileLocator, logger } from '@motajs/common'; import { IObjectMovable, IObjectMover } from '@user/data-common'; -import { - ILayerLocation, - IMapLayer, - IMapState, - IPassPredicate, - IStateBase -} from '@user/data-base'; +import { IStateBase } from '@user/data-base'; import { isNil } from 'lodash-es'; -import { IPathGraph, PathfindingGraphBuilder } from './graph'; +import { PathfindingFinder } from './finder'; import { IPathfinder, IPathfindingController, IPathfindingStep, IPathfindingSystem, - PathCostFunction, PathFallbackPolicy } from './types'; @@ -38,191 +31,6 @@ function hasMover(movable: IObjectMovable): movable is IMovableWithMover { //#endregion -//#region 寻路求解器 - -/** - * 寻路求解器,在动态构建的有向图上执行最小损失搜索。 - * 损失函数与通行性谓词均为可注入槽位,未注入损失函数时每格损失 1, - * 未注入谓词时所有边均不可通行 - */ -export class PathfindingFinder implements IPathfinder { - /** 当前对象对应的数据层对象 */ - readonly state: IStateBase; - - /** 绑定的地图状态对象,用于解析楼层 id */ - private maps: IMapState | null = null; - /** 绑定的地图图层,图节点来源 */ - private layer: IMapLayer | null = null; - /** 注入的通行性谓词,用于判定边可行性与终端节点 */ - private predicate: IPassPredicate | null = null; - /** 注入的损失函数,未注入时每格损失 1 */ - private cost: PathCostFunction | null = null; - /** 邻域方向组别,默认四正交方向 */ - private group: number = InternalDirectionGroup.Dir4; - - constructor(state: IStateBase) { - this.state = state; - } - - /** - * 绑定寻路所用的地图状态对象 - * @param maps 地图状态对象,传入 `null` 解绑 - */ - useMapState(maps: IMapState | null): void { - this.maps = maps; - } - - /** - * 绑定构建有向图所用的地图图层 - * @param layer 地图图层对象,传入 `null` 解绑 - */ - useMapLayer(layer: IMapLayer | null): void { - this.layer = layer; - } - - /** - * 设置自定义损失函数 - * @param cost 损失函数,传入 `null` 恢复默认 - */ - useCostFunction(cost: PathCostFunction | null): void { - this.cost = cost; - } - - /** - * 设置通行性谓词 - * @param predicate 通行性谓词,传入 `null` 恢复默认 - */ - usePassPredicate(predicate: IPassPredicate | null): void { - this.predicate = predicate; - } - - /** - * 设置寻路使用的朝向组 - * @param group 朝向组 - */ - useDirGroup(group: number): void { - this.group = group; - } - - /** - * 在当前绑定状态下执行寻路,返回损失最小的步骤序列。 - * 地图状态或图层未绑定、坐标越界等非法输入下告警并返回空数组; - * 目标不可达时同样返回空数组 - * @param start 寻路起始位置 - * @param target 寻路目标位置 - */ - find(start: ITileLocator, target: ITileLocator): IPathfindingStep[] { - const maps = this.maps; - const layer = this.layer; - if (isNil(maps) || isNil(layer)) { - logger.warn(173); - return []; - } - if ( - !layer.inMap(start.x, start.y) || - !layer.inMap(target.x, target.y) - ) { - logger.warn(173); - return []; - } - - // 数据端状态可变,每次寻路动态构建图,不做缓存 - const builder = new PathfindingGraphBuilder(); - builder.useMapState(maps); - builder.useMapLayer(layer); - builder.usePassPredicate(this.predicate); - builder.useDirGroup(this.group); - const graph = builder.build(); - return this.search(graph, start, target); - } - - /** - * 在有向图上执行 Dijkstra 最小损失搜索, - * 终端节点仅可作为路径终点,不可作为中间节点 - * @param graph 寻路有向图 - * @param start 寻路起始位置 - * @param target 寻路目标位置 - */ - private search( - graph: IPathGraph, - start: ITileLocator, - target: ITileLocator - ): IPathfindingStep[] { - const startIndex = start.y * graph.width + start.x; - const targetIndex = target.y * graph.width + target.x; - if (startIndex === targetIndex) return []; - if (!graph.nodes.has(startIndex) || !graph.nodes.has(targetIndex)) { - return []; - } - - const dist: Map = new Map(); - const prev: Map = new Map(); - const visited: Set = new Set(); - dist.set(startIndex, 0); - - while (true) { - let currIndex = -1; - let currDist = Infinity; - for (const [index, value] of dist) { - if (!visited.has(index) && value < currDist) { - currIndex = index; - currDist = value; - } - } - if (currIndex === -1) return []; - if (currIndex === targetIndex) break; - visited.add(currIndex); - const node = graph.nodes.get(currIndex); - if (!node || node.terminal) continue; - for (const edge of node.edges) { - if (visited.has(edge.to)) continue; - const next = graph.nodes.get(edge.to); - if (!next) continue; - if (next.terminal && edge.to !== targetIndex) continue; - const total = currDist + this.getNodeCost(next.block); - const known = dist.get(edge.to); - if (isNil(known) || total < known) { - dist.set(edge.to, total); - prev.set(edge.to, { - dir: edge.dir, - from: { x: node.x, y: node.y }, - to: { x: next.x, y: next.y } - }); - } - } - } - - const steps: IPathfindingStep[] = []; - let curr = targetIndex; - while (curr !== startIndex) { - const step = prev.get(curr); - if (!step) return []; - steps.push(step); - curr = step.from.y * graph.width + step.from.x; - } - steps.reverse(); - return steps; - } - - /** - * 获取进入指定位置节点的损失,非有限数或负数时 - * 告警并按默认损失 1 处理,保证搜索的非负权不变式 - * @param block 位置信息 - */ - private getNodeCost(block: ILayerLocation): number { - const cost = this.cost; - if (!cost) return 1; - const value = cost(block); - if (!Number.isFinite(value) || value < 0) { - logger.warn(174); - return 1; - } - return value; - } -} - -//#endregion - //#region 寻路系统 /** @@ -295,8 +103,7 @@ export class PathfindingSystem implements IPathfindingSystem { teleportTo(target: ITileLocator): IPathfindingController | null { const path = this.getPath(target); if (path.length === 0) return null; - const policy = this.policy; - if (isNil(policy) || policy(path)) { + if (isNil(this.policy) || this.policy(path)) { return this.startMove(path, false); } return this.startMove(path, true);