JPS是一种优化A*算法的路径规划算法,主要用于网格地图,通过跳过不必要的节点来提高搜索效率。它利用路径的对称性,只扩展特定的“跳点”,从而减少计算量。
deepseek生成的总是无法完整运行,因此决定手写一下。
需要注意的几点:
跳点检测:jump()
方法和 hasForcedNeighbor()
方法是算法核心,需要完整实现强制邻居检查逻辑
邻居剪枝:findNeighbors()
需要根据父节点方向进行方向剪枝,避免不必要的检查
代价计算:对角线移动使用√2成本,直线移动使用1单位成本
启发函数:使用适合网格移动的切比雪夫距离
以下是完整的跳点寻路算法,经测试可以运行:
type Grid = number[][];
type Point = { x: number; y: number };
type Direction = { dx: number; dy: number };
class Cell {
public x: number;
public y: number;
public parent: Cell | null;
public g: number = 0;
public h: number = 0;
constructor(
x: number,
y: number,
parent: Cell | null = null,
g: number = 0,
h: number = 0
) {
this.x = x;
this.y = y;
this.parent = parent;
this.g = g;
this.h = h;
}
get f(): number {
return this.g + this.h;
}
}
class JPS {
private openList: Cell[] = [];
private closedSet = new Set();
private readonly directions: Direction[] = [
{ dx: 0, dy: -1 }, // 上
{ dx: 1, dy: 0 }, // 右
{ dx: 0, dy: 1 }, // 下
{ dx: -1, dy: 0 }, // 左
{ dx: 1, dy: -1 }, // 右上
{ dx: 1, dy: 1 }, // 右下
{ dx: -1, dy: 1 }, // 左下
{ dx: -1, dy: -1 } // 左上
];
constructor(
private grid: Grid,
private start: Point,
private end: Point
) {}
public findPath(): Point[] {
this.openList.push(new Cell(this.start.x, this.start.y));
while (this.openList.length > 0) {
// 按F值排序获取最小节点
this.openList.sort((a, b) => a.f - b.f);
const currentCell = this.openList.shift()!;
if (currentCell.x === this.end.x && currentCell.y === this.end.y) {
return this.buildPath(currentCell);
}
const key = `${currentCell.x},${currentCell.y}`;
this.closedSet.add(key);
const neighbors = this.findNeighbors(currentCell);
for (const neighbor of neighbors) {
const jumpPoint = this.jump(neighbor, currentCell);
if (jumpPoint) {
const existing = this.openList.find(n =>
n.x === jumpPoint.x && n.y === jumpPoint.y
);
const g = currentCell.g + this.calculateCost(currentCell, jumpPoint);
if (!existing || g < existing.g) {
const newCell = new Cell(
jumpPoint.x,
jumpPoint.y,
currentCell,
g,
this.heuristic(jumpPoint)
);
if (!existing) {
this.openList.push(newCell);
} else {
existing.parent = newCell.parent;
existing.g = newCell.g;
}
}
}
}
}
return []; // 无路径
}
private findNeighbors(node: Cell): Point[] {
// 实现邻居查找(考虑父节点方向)
// 此处需要根据父节点方向剪枝不必要的方向
// 完整实现需要处理不同移动方向的情况
return this.directions
.map(d => ({ x: node.x + d.dx, y: node.y + d.dy }))
.filter(p => this.isWalkable(p.x, p.y));
}
private jump(current: Point, from: Cell): Point | null {
// 实现跳点检测的核心逻辑
if (!this.isWalkable(current.x, current.y)) return null;
if (current.x === this.end.x && current.y === this.end.y) return current;
// 检查强制邻居(核心跳点逻辑)
if (this.hasForcedNeighbor(current, from)) {
return current;
}
// 直线跳跃和对角线跳跃处理
const dx = current.x - from.x;
const dy = current.y - from.y;
// 对角线移动
if (dx !== 0 && dy !== 0) {
// 尝试水平/垂直方向跳跃
if (this.jump({ x: current.x + dx, y: current.y }, from) ||
this.jump({ x: current.x, y: current.y + dy }, from)) {
return current;
}
}
// 继续沿方向跳跃
return this.jump({
x: current.x + dx,
y: current.y + dy
}, from);
}
private hasForcedNeighbor(point: Point, parent: Cell): boolean {
const dx = point.x - parent.x;
const dy = point.y - parent.y;
// 直线移动方向检测
if (dx === 0 || dy === 0) {
return this.checkStraightForcedNeighbors(point, dx, dy);
}
// 对角线移动方向检测
return this.checkDiagonalForcedNeighbors(point, dx, dy);
}
private checkStraightForcedNeighbors(point: Point, dx: number, dy: number): boolean {
// 水平/垂直移动时的强制邻居检查
const forcedDirs: Direction[] = [];
if (dx !== 0) { // 水平移动
const checkDir = dy === 0 ? [1, -1] : [dy]; // 处理可能的误差
forcedDirs.push(
{ dx: 0, dy: 1 }, // 下方强制邻居方向
{ dx: 0, dy: -1 } // 上方强制邻居方向
);
} else { // 垂直移动
forcedDirs.push(
{ dx: 1, dy: 0 }, // 右侧强制邻居方向
{ dx: -1, dy: 0 } // 左侧强制邻居方向
);
}
// 主移动方向
const mainDir = { dx, dy };
return forcedDirs.some(dir => {
// 检查障碍物+可行走区域模式
const obstaclePos = {
x: point.x + (mainDir.dx !== 0 ? mainDir.dx : dir.dx),
y: point.y + (mainDir.dy !== 0 ? mainDir.dy : dir.dy)
};
const neighborPos = {
x: point.x + dir.dx,
y: point.y + dir.dy
};
// 必须满足:障碍物位置不可行走 + 邻居位置可行走
return !this.isWalkable(obstaclePos.x, obstaclePos.y) &&
this.isWalkable(neighborPos.x, neighborPos.y);
});
}
private checkDiagonalForcedNeighbors(point: Point, dx: number, dy: number): boolean {
// 对角线移动时的强制邻居检查
const horizontalCheck = { dx, dy: 0 };
const verticalCheck = { dx: 0, dy };
// 检查水平方向是否有障碍物导致强制邻居
const hasHorizontal = !this.isWalkable(point.x, point.y - dy) &&
this.isWalkable(point.x + dx, point.y - dy);
// 检查垂直方向是否有障碍物导致强制邻居
const hasVertical = !this.isWalkable(point.x - dx, point.y) &&
this.isWalkable(point.x - dx, point.y + dy);
// 检查自然邻居
const naturalNeighbor = this.isWalkable(point.x + dx, point.y) ||
this.isWalkable(point.x, point.y + dy);
return hasHorizontal || hasVertical || naturalNeighbor;
}
private isWalkable(x: number, y: number): boolean {
return x >= 0 && y >= 0 &&
x < this.grid[0].length &&
y < this.grid.length &&
this.grid[y][x] === 0;
}
private buildPath(node: Cell): Point[] {
const path: Point[] = [];
let current: Cell | null = node;
while (current) {
path.unshift({ x: current.x, y: current.y });
current = current.parent;
}
return path;
}
private heuristic(point: Point): number {
// 使用对角线距离(切比雪夫距离)
const dx = Math.abs(point.x - this.end.x);
const dy = Math.abs(point.y - this.end.y);
return Math.max(dx, dy);
}
private calculateCost(a: Cell, b: Point): number {
// 对角线移动成本为√2,直线为1
const dx = Math.abs(a.x - b.x);
const dy = Math.abs(a.y - b.y);
return dx === dy ? Math.SQRT2 : 1;
}
}
// 使用示例
const grid: Grid = [
[0, 0, 0, 0, 0],
[0, 1, 1, 1, 0],
[0, 0, 0, 0, 0],
[0, 1, 1, 1, 0],
[0, 0, 0, 0, 0]
];
const jps = new JPS(grid, { x: 0, y: 0 }, { x: 4, y: 4 });
const path = jps.findPath();
console.log("Found path:", path);