A星算法JS学习版

在这里插入图片描述

<template>
  <div class="btns">
    <VanButton size="small" type="primary" @click="start">开始寻路</VanButton>
  </div>
  <canvas
    id="canvas"
    width="1000"
    height="600"
    @click.left="leftClick"
    @click.right="rightClick"
  ></canvas>
</template>
<script setup lang="ts">
import { onMounted } from 'vue'
import DrawMap from './DrawMap.js'
import Astar from './AStar.js'

const gridSize = 20 // 网格长度
let canvas: any
let drawMap: any
let astar: any
let maxX = 0
let maxY = 0
onMounted(() => {
  canvas = document.querySelector('#canvas')
  maxX = Math.floor(canvas.width / gridSize)
  maxY = Math.floor(canvas.height / gridSize)
  drawMap = new DrawMap(canvas, gridSize)
  drawMap.init()
  const obstacles = drawMap.dynamicFill(500)
  astar = new Astar(maxX, maxY, obstacles)
})
interface Click {
  offsetX: number
  offsetY: number
}
// 设置起点
function leftClick(e: Click) {
  const x = Math.floor(e.offsetX / gridSize)
  const y = Math.floor(e.offsetY / gridSize)
  if (x > 0 && x < maxX && y > 0 && y < maxY) {
    drawMap.fill(x, y, 'green')
    astar.setStartPoint(x, y)
  }
}
// 设置终点
function rightClick(e: Click) {
  const x = Math.floor(e.offsetX / gridSize)
  const y = Math.floor(e.offsetY / gridSize)
  if (x > 0 && x < maxX && y > 0 && y < maxY) {
    drawMap.fill(x, y, 'red')
    astar.setEndPoint(x, y)
  }
}
function start() {
  astar.findPath()
  const path = astar.getFullpath()
  for (let i = 0; i < path.length; i++) {
    const point = path[i]
    setTimeout(() => {
      drawMap.fill(point.x, point.y, 'green')
    }, 100 * i)
  }
}
</script>
<style lang="less" scoped>
.btns {
  height: 100px;
  display: flex;
  justify-content: center;
  align-items: center;
}
#canvas {
  background: #fff;
}
</style>

class Point {
  x = 0
  y = 0
  f = 0 // f = g + h
  g = 0
  h = 0
  parent = null
  constructor(x, y, parent = null) {
    this.x = x
    this.y = y
    this.parent = parent
  }
}

export default class AStar {
  openList = [] // 待探索节点
  closedList = [] // 已探索节点
  startPoint = null
  endPoint = null
  finalPoint = null // 结果
  fullPath = [] // 完整路径
  obstacles = [] // 障碍
  maxX = 0 // 最大x坐标
  maxY = 0 // 最大y坐标
  constructor(maxX, maxY, obstacles = []) {
    this.startPoint = new Point(0, 0)
    this.endPoint = new Point(0, 0)
    this.openList.push(this.startPoint)
    this.maxX = maxX
    this.maxY = maxY
    this.obstacles = obstacles
  }
  // 设置起点
  setStartPoint(x, y) {
    this.startPoint = new Point(x, y)
    this.openList = [this.startPoint]
  }
  // 设置终点
  setEndPoint(x, y) {
    this.endPoint = new Point(x, y)
  }
  // 寻路
  findPath() {
    // 判断openList是否有值
    if (this.openList.length === 0) {
      console.log('死路')
      return
    }
    const current = this.openList[0]
    // 判断是否结束
    if (current.x === this.endPoint.x && current.y === this.endPoint.y) {
      console.log('寻路已结束')
      this.finalPoint = current
      return
    }
    // 左
    const xl = current.x - 1
    const yl = current.y
    const flagl = xl > 0 && this.canAdd(xl, yl)
    if (flagl) {
      this.addOpenList(xl, yl, current)
    }
    // 右
    const xr = current.x + 1
    const yr = current.y
    const flagr = xr < this.maxX && this.canAdd(xr, yr)
    if (flagr) {
      this.addOpenList(xr, yr, current)
    }
    // 上
    const xt = current.x
    const yt = current.y - 1
    const flagt = yt > 0 && this.canAdd(xt, yt)
    if (flagt) {
      this.addOpenList(xt, yt, current)
    }
    // 下
    const xb = current.x
    const yb = current.y + 1
    const flagb = yb < this.maxY && this.canAdd(xb, yb)
    if (flagb) {
      this.addOpenList(xb, yb, current)
    }
    // 删除current
    this.openList.shift()
    this.closedList.push(current)
    this.openList.sort((a, b) => a.f - b.f)
    this.findPath()
  }
  // 设置点位
  addOpenList(x, y, current) {
    const p = new Point(x, y, current)
    this.G(p)
    this.H(p)
    this.F(p)
    this.openList.push(p)
  }
  // 判断是否能添加至openlist
  canAdd(x, y) {
    return !(
      this.openList.find(item => item.x === x && item.y === y) ||
      this.closedList.find(item => item.x === x && item.y === y) ||
      this.obstacles.find(item => item.x === x && item.y === y)
    )
  }
  // 获取完整路径-收集parent
  getFullpath() {
    if (this.finalPoint) {
      this.fullPath.push({
        x: this.finalPoint.x,
        y: this.finalPoint.y,
      })
      let parent = this.finalPoint.parent
      while (parent) {
        this.fullPath.unshift({
          x: parent.x,
          y: parent.y,
        })
        parent = parent.parent
      }
    }
    return this.fullPath
  }
  F(p) {
    p.f = p.g + p.h
  }
  G(p) {
    p.g = p.parent.g + 1
  }
  H(p) {
    p.h = Math.sqrt(
      Math.pow(this.endPoint.x - p.x, 2) + Math.pow(this.endPoint.y - p.y, 2),
    ).toFixed(2)
  }
}

export default class DrawMap {
  ctx = null
  width = 0
  height = 0
  gridSize = 20 // 网格大小
  colorStep = 5 // 颜色步长
  constructor(canvas, gridSize) {
    this.ctx = canvas.getContext('2d')
    this.width = canvas.width
    this.height = canvas.height
    this.gridSize = gridSize
  }
  init() {
    this.drawGrid()
  }
  // 绘制网格
  drawGrid() {
    this.ctx.beginPath()
    for (let i = 0; i < this.width; i += this.gridSize) {
      this.ctx.moveTo(i, 0)
      this.ctx.lineTo(i, this.height)
    }
    for (let i = 0; i < this.height; i += this.gridSize) {
      this.ctx.moveTo(0, i)
      this.ctx.lineTo(this.width, i)
    }
    this.ctx.strokeStyle = '#ccc'
    this.ctx.stroke()
  }
  // 填充颜色
  fill(x, y, color = '#000') {
    this.ctx.beginPath()
    this.ctx.rect(
      x * this.gridSize,
      y * this.gridSize,
      this.gridSize,
      this.gridSize,
    )
    this.ctx.fillStyle = color
    this.ctx.fill()
  }
  // 动态填充
  dynamicFill(num = 100) {
    this.ctx.clearRect(0, 0, this.width, this.height)
    this.drawGrid()
    const arr = []
    for (let i = 0; i < num; i++) {
      const x = Math.floor(Math.random() * (this.width / this.gridSize))
      const y = Math.floor(Math.random() * (this.height / this.gridSize))
      const index = arr.findIndex(item => item.x === x && item.y === y)
      if (index === -1) {
        arr.push({ x, y })
        this.fill(x, y)
      }
    }
    return arr
  }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值