使用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<string>();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,直線為1const 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);

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/75429.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/75429.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/75429.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Jetpack Compose 狀態管理指南:從基礎到高級實踐

在Jetpack Compose中&#xff0c;界面狀態管理是構建響應式UI的核心。以下是Compose狀態管理的主要概念和實現方式&#xff1a; 基本狀態管理 1. 使用 mutableStateOf Composable fun Counter() {var count by remember { mutableStateOf(0) }Button(onClick { count }) {T…

vant4+vue3上傳一個pdf文件并實現pdf的預覽。使用插件pdf.js

注意下載的插件的版本"pdfjs-dist": "^2.2.228", npm i pdfjs-dist2.2.228 然后封裝一個pdf的遮罩。因為pdf文件有多頁&#xff0c;所以我用了swiper輪播的形式展示。因為用到移動端&#xff0c;手動滑動頁面這樣比點下一頁下一頁的方便多了。 直接貼代碼…

Leetcode hot 100(day 4)

翻轉二叉樹 做法&#xff1a;遞歸即可&#xff0c;注意判斷為空 class Solution { public:TreeNode* invertTree(TreeNode* root) {if(rootnullptr)return nullptr;TreeNode* noderoot->left;root->leftinvertTree(root->right);root->rightinvertTree(node);retu…

C,C++語言緩沖區溢出的產生和預防

緩沖區溢出的定義 緩沖區是內存中用于存儲數據的一塊連續區域&#xff0c;在 C 和 C 里&#xff0c;常使用數組、指針等方式來操作緩沖區。而緩沖區溢出指的是當程序向緩沖區寫入的數據量超出了該緩沖區本身能夠容納的最大數據量時&#xff0c;額外的數據就會覆蓋相鄰的內存區…

大數據(4)Hive數倉三大核心特性解剖:面向主題性、集成性、非易失性如何重塑企業數據價值?

目錄 背景&#xff1a;企業數據治理的困境與破局一、Hive數據倉庫核心特性深度解析1. ?面向主題性&#xff08;Subject-Oriented&#xff09;&#xff1a;從業務視角重構數據?2. ?集成性&#xff08;Integrated&#xff09;&#xff1a;打破數據孤島的統一視圖?3. ?非易失…

A股復權計算_前復權數據計算_終結章

目錄 前置&#xff1a; 計算方法推導 數據&#xff1a; 代碼&#xff1a; 視頻&#xff1a; 前置&#xff1a; 1 本系列將以 “A股復權計算_” 開頭放置在“隨想”專欄 2 權息數據結合 “PostgreSQL_” 系列博文中的股票未復權數據&#xff0c;可以自行計算復權日數據 …

Nature:新發現!首次闡明大腦推理神經過程

人類具有快速適應不斷變化的環境的認知能力。這種能力的核心是形成高級、抽象表示的能力&#xff0c;這些表示利用世界上的規律來支持泛化。然而&#xff0c;關于這些表征如何在神經元群中編碼&#xff0c;它們如何通過學習出現以及它們與行為的關系&#xff0c;人們知之甚少。…

Kotlin 集合函數:map 和 first 的使用場景

Kotlin 提供了豐富的集合操作函數&#xff0c;使開發者可以更加簡潔、高效地處理數據。其中&#xff0c;map 和 first 是兩個常用的函數&#xff0c;分別用于轉換集合和獲取集合中的第一個元素。 1. map 的使用場景 場景 1&#xff1a;對象列表轉換 在開發中&#xff0c;我們…

EIR管理中IMEI和IMSI信息的作用

在EIR&#xff08;設備身份注冊&#xff09;管理中&#xff0c;IMEI&#xff08;國際移動設備身份碼&#xff09;和IMSI&#xff08;國際移動用戶識別碼&#xff09;各自具有重要作用&#xff0c;以下是詳細介紹&#xff1a; IMEI的作用 設備身份識別&#xff1a;IMEI是移動設…

MAUI開發第一個app的需求解析:登錄+版本更新,用于喂給AI

vscode中MAUI框架已經搭好,用MAUI+c#webapi+orcl數據庫開發一個app, 功能是兩個界面一個登錄界面,登錄注冊常用功能,另一個主窗體,功能先空著,顯示“主要功能窗體”。 這是一個全新的功能,需要重零開始涉及所有數據表 登錄后檢查是否有新版本程序,自動更新功能。 1.用戶…

KUKA機器人查看運行日志的方法

對于KUKA機器人的運行日志都是可以查看和導出的&#xff0c;方便查找問題。KUKA機器人的運行日志查看方法如下&#xff1a; 1、在主菜單下&#xff0c;選擇【診斷】-【運行日志】-【顯示】下打開&#xff1b; 2、顯示出之前的機器人運行日志&#xff1b; 3、也可以通過【過濾器…

Kali Linux 2025.1a:主題煥新與樹莓派支持的深度解析

一、年度主題更新與桌面環境升級 Kali Linux 2025.1a作為2025年的首個版本&#xff0c;延續了每年刷新主題的傳統。本次更新包含全新的啟動菜單、登錄界面及桌面壁紙&#xff0c;涵蓋Kali標準版和Kali Purple版本。用戶可通過安裝kali-community-wallpapers包獲取社區貢獻的額…

【UVM學習筆記】更加靈活的UVM—通信

系列文章目錄 【UVM學習筆記】UVM基礎—一文告訴你UVM的組成部分 【UVM學習筆記】UVM中的“類” 文章目錄 系列文章目錄前言一、TLM是什么&#xff1f;二、put操作2.1、建立PORT和EXPORT的連接2.2 IMP組件 三、get操作四、transport端口五、nonblocking端口六、analysis端口七…

uni-app項目上傳至gitee方法詳細教程

1. 準備工作 1.1 安裝 Git 下載并安裝 Git&#xff1a;前往 Git 官網&#xff0c;根據操作系統下載安裝包。 配置用戶名和郵箱&#xff08;需與 Gitee 賬號一致&#xff09;&#xff1a; git config --global user.name "你的Gitee用戶名" git config --global use…

走向多模態AI之路(三):多模態 AI 的挑戰與未來

目錄 前言一、多模態 AI 真的成熟了嗎&#xff1f;二、多模態 AI 的主要挑戰2.1 計算資源消耗&#xff1a;模型復雜度帶來的成本問題2.2 數據標注困難&#xff1a;跨模態數據集的挑戰2.3 對齊和融合的難點2.4 泛化能力與魯棒性2.5 倫理與隱私問題 三、研究方向與未來發展3.1 輕…

STM32單片機入門學習——第12節: [5-2]對射式紅外傳感器計次旋轉編碼器計次

寫這個文章是用來學習的,記錄一下我的學習過程。希望我能一直堅持下去,我只是一個小白,只是想好好學習,我知道這會很難&#xff0c;但我還是想去做&#xff01; 本文寫于&#xff1a;2025.04.03 STM32開發板學習——第12節: [5-2]對射式紅外傳感器計次&旋轉編碼器計次 前言…

匯編學習之《jcc指令》

JCC&#xff08;Jump on Condition Code&#xff09;指的是條件跳轉指令&#xff0c;c中的就是if-else, while, for 等分支循環條件判斷的邏輯。它包括很多指令集&#xff0c;各自都不太一樣&#xff0c;接下來我盡量將每一個指令的c 源碼和匯編代碼結合起來看&#xff0c;加深…

深度解析算法之滑動窗口

12滑動窗口—將 x 減到 0 的最小操作數 題目傳送門 題目描述&#xff1a; 給你一個整數數組 nums 和一個整數 x 。每一次操作時&#xff0c;你應當移除數組 nums 最左邊或最右邊的元素&#xff0c;然后從 x 中減去該元素的值。請注意&#xff0c;需要 修改 數組以供接下來的操…

[MySQL初階]MySQL表的操作

MySQL表的操作 1. 創建表2. 查看表結構3. 修改表&#xff08;修改表的屬性而非表的數據&#xff09;4. 刪除表 1. 創建表 語法&#xff1a; CREATE TABLE table_name (field1 datatype,field2 datatype,field3 datatype ) character set 字符集 collate 校驗規則 engine 存儲…

sqlalchemy詳細介紹以及使用方法

SQLAlchemy是一個Python的ORM&#xff08;對象關系映射&#xff09;工具&#xff0c;它允許開發者使用Python代碼來操作數據庫而不必直接編寫SQL語句。SQLAlchemy提供了一種抽象層&#xff0c;使開發者可以通過簡單的Python對象來表示數據庫表和記錄&#xff0c;從而實現對數據…