mirror of
https://github.com/motajs/template.git
synced 2026-09-12 09:48:49 +08:00
refactor(02-02): comply with updated style rules, extract finder and drop temp function consts
This commit is contained in:
parent
1372e233eb
commit
cbcaec83dc
@ -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,
|
||||
|
||||
195
packages-user/data-system/src/path/finder.ts
Normal file
195
packages-user/data-system/src/path/finder.ts
Normal file
@ -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<number, number> = new Map();
|
||||
const prev: Map<number, IPathfindingStep> = new Map();
|
||||
const visited: Set<number> = 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
|
||||
@ -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
|
||||
};
|
||||
|
||||
/** 汇入图块:仅可从左方向进入,不可离开 */
|
||||
|
||||
@ -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 });
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
export * from './finder';
|
||||
export * from './graph';
|
||||
export * from './system';
|
||||
export * from './types';
|
||||
|
||||
@ -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<number, number> = new Map();
|
||||
const prev: Map<number, IPathfindingStep> = new Map();
|
||||
const visited: Set<number> = 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);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user