美文网首页
JS实现深度+启发(Heuristic DFS)寻路算法

JS实现深度+启发(Heuristic DFS)寻路算法

作者: 一个灰 | 来源:发表于2018-10-16 14:17 被阅读0次

本人在业余时间开发一个兔子围城游戏的时候,在网上寻找一种高效的寻路算法。最终找到这篇文章
四种寻路算法计算步骤比较
遂从C++代码移植到了AS(Flash版,使用Player.IO作为后端),现在又从AS移植到了JS(微信小游戏需要),并使用ES6语法进行优化。使得代码尽量精简。

源码

const dx = [0, 0, -1, 1]; //四种移动方向对x和y坐标的影响
const dy = [-1, 1, 0, 0];
const Position = {
    move(direction) {
        this.x += dx[direction];
        this.y += dy[direction];
        this.pass = true
        this.createOrders()
    },
    back(direction) {
        this.pass = false
        this.order.p = 0
        this.x -= dx[direction];
        this.y -= dy[direction];
    },
    forwardPos(direction) {
        return Object.setPrototypeOf({ x: this.x + dx[direction], y: this.y + dy[direction] }, Position)
    },
    set pass(v) {
        this.past[this] = v
    },
    get pass() {
        return this.past[this]
    },
    ActOK(currAct) {
        var tempPos = this.forwardPos(currAct)
        if (tempPos.x > 8 || tempPos.x < 0 || tempPos.y > 8 || tempPos.y < 0 || tempPos.pass || this.chessboard.getGridByXY(this.x, this.y).walls[currAct]) //坐标出界?|| 已到过?
            return false;
        this.move(currAct)
        return true;
    },
    get distance() {
        return Math.abs(this[this.target[0]] - this.target[1])
    },
    createOrders() {
        var directions = [0, 1, 2, 3].map(i => ({ i, dis: this.forwardPos(i).distance }));
        directions.sort((a, b) => a.dis - b.dis) //根据距离排序,先探索距离近的方向
        var order = directions.map(x => x.i)
        order.push(-1)
        order.p = 0
        this.orders[this] = order
    },
    getNextAct() {
        return this.order[this.order.p++]
    },
    get order() {
        return this.orders[this]
    },
    toString() { return this.x + "," + this.y }
}
export default function({ pos: { x, y }, target }) {
    var pos = Object.setPrototypeOf({ x, y }, Object.assign(Position, { past: {}, orders: {}, chessboard: this, target }));
    pos.pass = true
    pos.createOrders()
    for (var path = [];;) {
        var currAct = pos.getNextAct()
        if (currAct < 0) {
            if (path.length)
                pos.back(path.pop())
            else return true
        } else if (pos.ActOK(currAct)) {
            if (pos.distance == 0) return false
            path.push(currAct)
        }
    }
    return true;
}

分析

基于游戏本身的规则,这个算法是四方向的,首先定义每一个方向的编号
0:↑ 1:↓ 2:← 3:→ 即[上,下,左,右]
或者这么想象

  0
2   3
  1

所以下面的代码就好理解了

const dx = [0, 0, -1, 1]; //四种移动方向对x和y坐标的影响
const dy = [-1, 1, 0, 0];

如果此时方向向上,即编号为0,我们取得的dx[0]就是x的变化即0,没有变化
dy[0]是y轴的变化-1代表向上走一格,其他以此类推。

下面定义了一个Position对象,一会儿我们会讲
我们先看主逻辑

var pos = Object.setPrototypeOf({ x, y }, Object.assign(Position, { past: {}, orders: {}, chessboard: this, target }));
pos.pass = true
pos.createOrders()

这三行代码用了一些奇技淫巧
我们需要新建一个pos对象,x,y属性是传入的起点坐标。 Object.setPrototypeOf用来给这个对象搞一个父类(这一点不同于其他语言)。这个父类是Position对象,但为了每次初始化方便就用Object.assign给它强行覆盖(没有的时候会创建)属性
past用于存储已经寻路过的坐标,orders存放最优方向顺序,后面两个参数和业务逻辑有关先忽略
pos.pass = true 用来指明当前坐标已经经过,其执行过程是这样的。
会调用Position的set pass方法,即

set pass(v) {
     this.past[this] = v
}

其中this.past是之前初始化的past对象,方括号赋值,就是以this作为键。此时js会进行转换,this转成string类型,就会去调用

toString() { return this.x + "," + this.y }

好吧,我承认是装逼写法而已。应该这么写this.past[this.x + "," + this.y] = v
pos.createOrders() 用于创建优先方向列表

createOrders() {
    var directions = [0, 1, 2, 3].map(i => ({ i, dis: this.forwardPos(i).distance }));
    directions.sort((a, b) => a.dis - b.dis) //根据距离排序,先探索距离近的方向
    var order = directions.map(x => x.i)
    order.push(-1)
    order.p = 0
    this.orders[this] = order
}

这个所谓的优先方向,就是启发式搜索算法里面的东西。就是朝4个方向前进一步后和目标距离进行比较,如果更接近目标那么就是优先的方向,目的是加快朝目标寻路。
我们把列表保存,一会儿要用到。push(-1)的目的是代表方向都搜索结束的结束标志。
计算距离首先调用forwardPos函数

    forwardPos(direction) {
        return Object.setPrototypeOf({ x: this.x + dx[direction], y: this.y + dy[direction] }, Position)
    },

这个函数再次使用给对象指定父类的方式返回一个新的坐标的pos对象,这样可以方便的调用Position中的方法。紧接着就调用了distance

    get distance() {
        return Math.abs(this[this.target[0]] - this.target[1])
    },

返回了两点之间的距离,其中target[0]存放的是目标属性名称(‘x’或者‘y’),target[1]存放的是目标值。由于游戏规则设定,目标不是一个点,而是一条线,所以只需判断其中一个坐标即可。

接下来进入主循环

    for (var path = [];;) {
        var currAct = pos.getNextAct()
        if (currAct < 0) {
            if (path.length)
                pos.back(path.pop())
            else return true
        } else if (pos.ActOK(currAct)) {
            if (pos.distance == 0) return false
            path.push(currAct)
        }
    }

其实就是一个死循环,path变量存放的是路径数组,其元素是行走方向,用于回退。
首先我们调用getNextAct方法,用于获取下一步的方向

    getNextAct() {
        return this.order[this.order.p++]
    },

this.order.p存放的是优先列表的下标,从0开始,我们尝试每一个方向的探索。
if (currAct < 0) 判断了是否在这个位置的4个方向都已经探索结束。

if (path.length)
  pos.back(path.pop())
else return true

上面这段表示如果路径不为空,则后退一步,否则说明无路可退,搜索结束,不可达。
后退函数

    back(direction) {
        this.pass = false
        this.order.p = 0
        this.x -= dx[direction];
        this.y -= dy[direction];
    },

把当前坐标设置为没有经过,方向列表下标恢复为0,坐标朝反方向移动一格

下面我们看如果该方向尚未探索的逻辑

        } else if (pos.ActOK(currAct)) {
            if (pos.distance == 0) return false
            path.push(currAct)
        }

我们先调用ActOK,判断此路是否可行

    ActOK(currAct) {
        var tempPos = this.forwardPos(currAct)
        if (tempPos.x > 8 || tempPos.x < 0 || tempPos.y > 8 || tempPos.y < 0 || tempPos.pass || this.chessboard.getGridByXY(this.x, this.y).walls[currAct]) //坐标出界?|| 已到过?
            return false;
        this.move(currAct)
        return true;
    },

tempPos时临时向前走一步,判断是否撞墙或者出界,如果可以走,那么就真的走一步,调用move函数

    move(direction) {
        this.x += dx[direction];
        this.y += dy[direction];
        this.pass = true
        this.createOrders()
    },

坐标根据方向改变,设置当前路径已经走过,然后再次调用获取优先方向的函数。

 if (pos.distance == 0) return false

代表已经抵达终点,路径可达,退出循环。
否则path.push(currAct)把改方向放入路径数组中,循环一下一步。

相关文章

  • JS实现深度+启发(Heuristic DFS)寻路算法

    本人在业余时间开发一个兔子围城游戏的时候,在网上寻找一种高效的寻路算法。最终找到这篇文章四种寻路算法计算步骤比较遂...

  • 博客园转载

    启发式算法(Heuristic Algorithm) 启发式算法(Heuristic Algorithm)有不同的...

  • JS算法和数据结构

    A-star寻路 寻路模式深度优先搜索广度优先搜索启发式搜索 -> A*算法估价函数 估价函数:f(n) = g(...

  • (3.7学堂在线python学习笔记)

    @[TOC](3.7学堂在线python学习笔记) # 重要笔记 1. 启发式算法 启发式算法(heuristic...

  • 算法2

    算法的分类 精确算法(exact algorithm),总能保证求得问题的解 启发式算法(heuristic al...

  • BFS

    [TOC] BFS 和 DFS BFS广度有限搜索和DFS深度优先搜索算法是特别常用的两种算法 DFS 算法就是回...

  • A*算法 和 最佳优先搜索算法(Best-First-Searc

    BFS算法 算法原理 最佳优先搜索算法是一种启发式搜索算法(Heuristic Algorithm),其基于广度优...

  • Lecture 1-2

    P19.Optimizing and heuristic algorithms 最优解(优化算法)和启发式的区别、...

  • DFS算法

    DFS算法 DFS算法即:Depth First Search,深度优先搜索。这个算法的关键是解决“当下如何做”,...

  • Depth First Search

    概述 DFS(Depth First Search) => 深度优先搜索算法 => 递归与回溯,通常隐含了栈的实现...

网友评论

      本文标题:JS实现深度+启发(Heuristic DFS)寻路算法

      本文链接:https://www.haomeiwen.com/subject/eppczftx.html