mirror of
https://github.com/motajs/template.git
synced 2026-09-16 03:58:51 +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 条件缺陷的修复铺设回归用例
|
// 验证 L0 ObjectMover 单步移动后的坐标回写行为(正交 / 斜向 / 传送步),为 mover.ts:651 条件缺陷的修复铺设回归用例
|
||||||
import { beforeAll, describe, expect, it, vi } from 'vitest';
|
import { beforeAll, describe, expect, it, vi } from 'vitest';
|
||||||
import { type ITileLocator } from '@motajs/common';
|
import { type ITileLocator } from '@motajs/common';
|
||||||
import {
|
import { FaceDirection } from '@user/data-common';
|
||||||
FaceDirection
|
|
||||||
} from '@user/data-common';
|
|
||||||
import {
|
import {
|
||||||
type IObjectMovable,
|
type IObjectMovable,
|
||||||
type ObjectMoveStep,
|
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 {
|
import {
|
||||||
type IDataCommon,
|
type IDataCommon,
|
||||||
type IFaceHandler,
|
type IFaceHandler,
|
||||||
type IGameMap,
|
|
||||||
type IPassCheckHandler,
|
|
||||||
type IPassPredicate,
|
|
||||||
type ITileStore
|
type ITileStore
|
||||||
} from '@user/data-common';
|
} from '@user/data-common';
|
||||||
|
import {
|
||||||
|
type IGameMap,
|
||||||
|
type IPassCheckHandler,
|
||||||
|
type IPassPredicate
|
||||||
|
} from '@user/data-base';
|
||||||
import { InternalDirectionGroup } from '@motajs/common';
|
import { InternalDirectionGroup } from '@motajs/common';
|
||||||
|
|
||||||
vi.hoisted(() => {
|
vi.hoisted(() => {
|
||||||
@ -87,7 +89,8 @@ const ONEWAY_TILE: TestTileDefinition = {
|
|||||||
num: 3,
|
num: 3,
|
||||||
id: 'oneway',
|
id: 'oneway',
|
||||||
outPass: 0b0010,
|
outPass: 0b0010,
|
||||||
inPass: 0
|
inPass: 0,
|
||||||
|
eventPass: true
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 汇入图块:仅可从左方向进入,不可离开 */
|
/** 汇入图块:仅可从左方向进入,不可离开 */
|
||||||
|
|||||||
@ -194,9 +194,10 @@ export class PathfindingGraphBuilder implements IPathfindingGraphBuilder {
|
|||||||
floorId,
|
floorId,
|
||||||
state
|
state
|
||||||
};
|
};
|
||||||
const predicate = this.predicate;
|
if (isNil(this.predicate) || !this.predicate.canPass(handler)) {
|
||||||
if (isNil(predicate) || !predicate.canPass(handler)) continue;
|
continue;
|
||||||
if (predicate.shouldHit(handler)) {
|
}
|
||||||
|
if (this.predicate.shouldHit(handler)) {
|
||||||
terminals.add(ny * width + nx);
|
terminals.add(ny * width + nx);
|
||||||
}
|
}
|
||||||
edges.push({ dir, to: ny * width + nx });
|
edges.push({ dir, to: ny * width + nx });
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
export * from './finder';
|
||||||
export * from './graph';
|
export * from './graph';
|
||||||
export * from './system';
|
export * from './system';
|
||||||
export * from './types';
|
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 { IObjectMovable, IObjectMover } from '@user/data-common';
|
||||||
import {
|
import { IStateBase } from '@user/data-base';
|
||||||
ILayerLocation,
|
|
||||||
IMapLayer,
|
|
||||||
IMapState,
|
|
||||||
IPassPredicate,
|
|
||||||
IStateBase
|
|
||||||
} from '@user/data-base';
|
|
||||||
import { isNil } from 'lodash-es';
|
import { isNil } from 'lodash-es';
|
||||||
import { IPathGraph, PathfindingGraphBuilder } from './graph';
|
import { PathfindingFinder } from './finder';
|
||||||
import {
|
import {
|
||||||
IPathfinder,
|
IPathfinder,
|
||||||
IPathfindingController,
|
IPathfindingController,
|
||||||
IPathfindingStep,
|
IPathfindingStep,
|
||||||
IPathfindingSystem,
|
IPathfindingSystem,
|
||||||
PathCostFunction,
|
|
||||||
PathFallbackPolicy
|
PathFallbackPolicy
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
@ -38,191 +31,6 @@ function hasMover(movable: IObjectMovable): movable is IMovableWithMover {
|
|||||||
|
|
||||||
//#endregion
|
//#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 寻路系统
|
//#region 寻路系统
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -295,8 +103,7 @@ export class PathfindingSystem implements IPathfindingSystem {
|
|||||||
teleportTo(target: ITileLocator): IPathfindingController | null {
|
teleportTo(target: ITileLocator): IPathfindingController | null {
|
||||||
const path = this.getPath(target);
|
const path = this.getPath(target);
|
||||||
if (path.length === 0) return null;
|
if (path.length === 0) return null;
|
||||||
const policy = this.policy;
|
if (isNil(this.policy) || this.policy(path)) {
|
||||||
if (isNil(policy) || policy(path)) {
|
|
||||||
return this.startMove(path, false);
|
return this.startMove(path, false);
|
||||||
}
|
}
|
||||||
return this.startMove(path, true);
|
return this.startMove(path, true);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user