A* 自动寻路算法-JavaScript

效果图

代码

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>A star</title>
</head>

<body style="margin: 0;">
  <canvas style="border: 2px solid gray;"></canvas>
  <p>起点:左上角</p>
  <p>终点:右下角</p>
  <p>蓝色:路线</p>
  <p>黑色:障碍物</p>
  <p>绿色:未探索的路</p>
  <p>红色:已探索的路</p>
</body>
<script>
  /*
   * A* 算法
   * */
  const canvas = document.querySelector('canvas')
  const ctx = canvas.getContext('2d')

  const board = []
  const openSet = [], closedSet = []
  let width = height = 30
  let start, end, path = [], over = false
  let current
  let numOfLoops = 0
  let TIME = 100
  canvas.width = 15 * width
  canvas.height = 15 * height + 50


  class Spot {
    constructor(x, y) {
      this.f = this.g = this.h = 0
      this.x = x
      this.y = y
      this.neighbours = [] // 邻居
      this.previous = null // 上一个
      this.isObstacle = Math.random() < 0.3 // 是否为障碍
    }
  }
  // 格子结构
  for (let x = 0; x < width; x++) {
    board.push([])
    for (let y = 0; y < height; y++) {
      board[x][y] = new Spot(x, y)
    }
  }

  // 设置开始和结束点,并给他们设置不是障碍物
  start = board[0][0]
  end = board[width - 1][height - 1]
  start.isObstacle = false
  end.isObstacle = false

  // 把起点加入openSet
  openSet.push(start)

  // 画格子
  function displayBoard(board) {
    ctx.clearRect(0, 0, canvas.width, canvas.height) // 先清空

    // 画障碍物和路
    for (const arr in board) {
      for (const tile in board[arr]) {
        let t = board[arr][tile]
        // 如果是障碍物就画成黑格子
        if (t.isObstacle) {
          ctx.fillStyle = 'black'
          ctx.fillRect(t.x * 15, t.y * 15, 15, 15)
        } else {
          ctx.fillStyle = '#ccc'
          ctx.fillRect(t.x * 15, t.y * 15, 15 - 1, 15 - 1)
        }
      }
    }
    // 画已经走过的格子
    // console.log(closedSet);
    for (const tile of closedSet) {
      ctx.fillStyle = 'red'
      ctx.fillRect(tile.x * 15, tile.y * 15, 15 - 1, 15 - 1)
    }
    // 画已经在openset里的格子
    for (const tile of openSet) {
      ctx.fillStyle = 'greenyellow'
      ctx.fillRect(tile.x * 15, tile.y * 15, 15 - 1, 15 - 1)
    }
    // 画当前所走的路
    if (current) {
      let temp = current
      path = [temp]
      // 如果当前格子有上一个,就加入path,并把temp重新赋值
      while (temp.previous) {
        path.push(temp.previous)
        temp = temp.previous
      }
      // 给path格子加颜色
      for (const tile of path) {
        ctx.fillStyle = 'blue'
        ctx.fillRect(tile.x * 15, tile.y * 15, 15 - 1, 15 - 1)
      }
    }
    // 画字
    ctx.fillStyle = 'black'
    ctx.font = '20px 黑体'
    ctx.fillText('A* Algorithm', 10, canvas.height - 15)
    // 已走步数
    ctx.fillText('Total loops:' + numOfLoops, canvas.width - 170, canvas.height - 15)
  }

  function update() {
    numOfLoops++
    // 没有要走的格子,结束
    if (!openSet.length) {
      alert('死路一条!')
      over = true
    } else {
      let winner = 0

      // 选出f值最小的
      for (let i = 0; i < openSet.length; i++) {
        if (openSet[i].f < openSet[winner].f) {
          winner = i
        }
      }

      // 当前f值最小的格子
      current = openSet[winner]
      // console.log(current);

      // 判断有没有到终点 如果到达终点,那么回溯走过的格子,如果没有,加入closedSet
      if (current === end) {
        let temp = current
        path = [temp]

        while (temp.previous) {
          path.push(temp.previous)
          temp = temp.previous
        }
        console.log(path);
        over = true
      }
      closedSet.push(current)
      openArrRemoveCurrent(openSet, current)

      for (let n of current.neighbours) {
        // 如果这个邻居已经被加入closedSet,就可以跳出本次循环
        if (closedSet.includes(n)) { continue }
        // 当前位置的周围需要当前的g+1
        let tempG = current.g + 1

        if (openSet.includes(n)) {
          if (tempG < n.g) {
            n.g = tempG
          }
        } else {
          n.g = tempG
          openSet.push(n)
        }
        n.h = heuristic(n, end)
        n.f = n.g + n.h
        n.previous = current
      }
    }
    
    displayBoard(board)
    if (!over) {
      // requestAnimationFrame(update())
      // update()
      window.setTimeout(update, TIME)
    }
  }
  // 删除openSet已经走过的
  function openArrRemoveCurrent(openSet, ele) {
    for (let i = openSet.length - 1; i >= 0; i--) {
      if (openSet[i] === ele) {
        openSet.splice(i, 1)
      }
    }
  }
  // 添加邻居
  function addNeighbours() {
    for (let x = 0; x < width; x++) {
      for (let y = 0; y < height; y++) {
        let spot = board[x][y]
        let left = getTileAtPos(x - 1, y)
        let right = getTileAtPos(x + 1, y)
        let up = getTileAtPos(x, y - 1)
        let down = getTileAtPos(x, y + 1)

        // 判断是否是障碍,如果不是,就是邻居
        left && !left.isObstacle && spot.neighbours.push(left)
        right && !right.isObstacle && spot.neighbours.push(right)
        up && !up.isObstacle && spot.neighbours.push(up)
        down && !down.isObstacle && spot.neighbours.push(down)
      }
    }
  }
  // 判断spot是否不存在
  function getTileAtPos(x, y) {
    if (typeof board[x] === 'undefined' || typeof board[x][y] === 'undefined') return false
    return board[x][y]
  }
  // 曼哈顿距离算法
  function heuristic(a, b) {
    return dist(a.x, a.y, b.x, b.y)
  }
  function dist(x1, y1, x2, y2) {
    let a = x1 - x2
    let b = y1 - y2
    return Math.abs(a) + Math.abs(b)
  }
  addNeighbours()
  update()
</script>

</html>
posted @ 2022-10-21 08:41  d浩然  阅读(100)  评论(0编辑  收藏  举报