A* search algorithm

By Jiajian Chan https://pigjian.com

by Chan Jiajian

HTML

<link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/4.0.0-alpha.6/css/bootstrap.css">
<script src="//cdn.bootcss.com/vue/2.3.4/vue.min.js"></script>
<div id="app">
    <h6>点击第一下空白方块设定起点(起点为红色方块) <div class="column red"></div>,点击第二个空白方块设置终点(再次点击重置起点)。</h6>
    <h6>正确的路线起点为红边绿块 <div class="column red green"></div>,终点是蓝边绿块 <div class="column blue green"></div></h6>
    <div class="container m-4">
      <div class="row" v-for="items in elements">
          <div class="column"
               :class="matchClass(item)"
               :id="item.x + '-' + item.y"
               v-for="item in items" @click="setElement(item)"></div>
      </div>
    </div>
    <button type="button" class="button" @click="setRandomObstacle()">随机障碍</button>
    <button type="button" class="button" @click="clearObstacle()">清空障碍</button>
    <button type="button" class="button" @click="setObstacleMode()">障碍模式</button>
    <button type="button" class="button" @click="setStartAndEnd()">起始模式</button>
    <h6>起始模式(默认):可设置起始点和终点 </h6>
    <h6>障碍模式:点击开启后,可点击方块设置障碍物 </h6>
</div>

CSS

#app {
    width: 767px;
    margin: 50px auto;
}
.row {
    height: 30px;
}
.column {
    display: inline-block;
    width: 30px;
    height: 30px;
    border: 1px solid #ECF0F1;
}
.black {
    background-color: #2C3E50;
}
.red {
    border: 1px solid #E74C3C;
    background-color: #E74C3C;
}
.blue {
    border: 1px solid #3498DB;
    background-color: #3498DB;
}
.green {
    background-color: #1ABC9C;
}
.button {
    color: #1B9AF7;
    background-color: #EEE;
    border-color: #EEE;
    font-weight: 300;
    font-size: 14px;
    font-family: "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
    text-decoration: none;
    text-align: center;
    line-height: 20px;
    height: 30px;
    padding: 5px 20px;
    margin: 10px;
    display: inline-block;
    appearance: none;
    cursor: pointer;
    border: none;
    -webkit-box-sizing: border-box;
    -moz-box-sizing: border-box;
    box-sizing: border-box;
    -webkit-transition-property: all;
    transition-property: all;
    -webkit-transition-duration: .3s;
    transition-duration: .3s;
    border-radius: 3px;
}
.button:hover {
    color: #fff;
    background-color: #1B9AF7;
    border-color: #1B9AF7;
}
.button:focus {
    outline: none;
}

JavaScript

new Vue({
	el: '#app',
  data: {
  	openList: [],
  	closeList: [],
  	lineLength: 10,
  	elements: [],
  	start: null,
  	end: null,
  	calculating: false,
  	range: {
  	  x: 8,
  	  y: 8
  	},
  	obstacles: [],
  	mode: 'start-end',
  	viablePath: [],
  },
  mounted() {
    this.elements = this.createElements(this.range.y, this.range.x)
  },
  methods: {
    // 计算 G 值
    calculateG(element, length) {
      if (element.previousElement == null) {
        return length
      } else {
        return element.previousElement.g + length
      }
    },
    // 计算 H 值
    calculateH(element) {
      return (Math.abs(element.x - this.end.x) + Math.abs(element.y - this.end.y)) * 10
    },
    // 计算 F 值
    calculateF(element) {
      return element.g + element.h
    },
    // 创建元素阵
    createElements(row, column) {
      let elements = []
      for (let i = 0; i < row; i++) {
        for (let j = 0; j < column; j++) {
          elements[i] || (elements[i] = [])
          let element = {
            status: 1,
            previousElement: null,
            x: 0,
            y: 0,
            f: 0,
            h: 0,
            g: 0,
          }

          element.x = j
          element.y = i

          elements[i][j] = element
        }
      }

      return elements
    },
    // 获取路径
    getPath(element, target) {
      element.previousElement = null

      this.openList.push(element)

      this.search(target)
    },
    // 搜索路径
    search(target) {
      let element = null
      let find = false
      let i = 0
      while(this.openList.length > 0) {
        // 获取 F 最小的元素
        element = this.openList[0]

        // 元素与目标元素相等,则退出循环
        if (element.x == target.x && element.y == target.y) {
          find = true
          break;
        }

        // 上方元素
        if ((element.y - 1) >= 0) {
          this.checkElement(element.x, element.y - 1, element, this.lineLength)
        }

        // 下方元素
        if ((element.y + 1) < this.range.y) {
         ...