使用typescript实现游戏中的JPS跳点寻路算法

JPS是一种优化A*算法的路径规划算法,主要用于网格地图,通过跳过不必要的节点来提高搜索效率。它利用路径的对称性,只扩展特定的“跳点”,从而减少计算量。

deepseek生成的总是无法完整运行,因此决定手写一下。

需要注意的几点:

  1. 跳点检测jump() 方法和 hasForcedNeighbor() 方法是算法核心,需要完整实现强制邻居检查逻辑

  2. 邻居剪枝findNeighbors() 需要根据父节点方向进行方向剪枝,避免不必要的检查

  3. 代价计算:对角线移动使用√2成本,直线移动使用1单位成本

  4. 启发函数:使用适合网格移动的切比雪夫距离

以下是完整的跳点寻路算法,经测试可以运行:

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

你可能感兴趣的:(typescript,游戏,算法)