refactor(02-02): comply with updated style rules, extract finder and drop temp function consts

This commit is contained in:
unanmed 2026-09-09 17:44:31 +08:00
parent 1372e233eb
commit cbcaec83dc
6 changed files with 212 additions and 207 deletions

View File

@ -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,

View 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

View File

@ -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
};
/** 汇入图块:仅可从左方向进入,不可离开 */

View File

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

View File

@ -1,3 +1,4 @@
export * from './finder';
export * from './graph';
export * from './system';
export * from './types';

View File

@ -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);