astar.go 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. package astar
  2. import (
  3. "f1-game/internal/constant"
  4. "f1-game/internal/types"
  5. "fmt"
  6. "math"
  7. "slices"
  8. "sort"
  9. clog "github.com/cherry-game/cherry/logger"
  10. )
  11. // AStar A*寻路算法结构
  12. type AStar struct {
  13. config Config
  14. cache map[int32]*Node
  15. openList []*Node
  16. closeList types.Set[*Node]
  17. parentMap map[*Node]*Node // 当前寻路的父节点
  18. gMap map[*Node]int32 // 当前寻路的g值
  19. hMap map[*Node]int32 // 当前寻路的h值
  20. // mapData [][]int32
  21. }
  22. func New(c Config) *AStar {
  23. // if c.Row <= 0 || c.Column <= 0 {
  24. // clog.Fatal("map width or height must be greater than 0")
  25. // }
  26. // if len(c.MapData) == 0 {
  27. // clog.Fatal("map data is empty")
  28. // }
  29. // if c.Column > int32(len(c.MapData)) || c.Row > int32(len(c.MapData[0])) {
  30. // clog.Fatal("map width or height must be less than map data size")
  31. // }
  32. instance := &AStar{config: c}
  33. instance.init()
  34. return instance
  35. }
  36. func (p *AStar) init() {
  37. p.closeList = types.NewSet[*Node]()
  38. p.parentMap = make(map[*Node]*Node)
  39. p.gMap = make(map[*Node]int32)
  40. p.hMap = make(map[*Node]int32)
  41. p.cache = make(map[int32]*Node)
  42. for x := range p.config.Width {
  43. for y := range p.config.Height {
  44. value := p.config.MapData[x][y]
  45. if constant.IsWalkableTile(value) {
  46. // 加上偏移量
  47. p.addNode(NewNode(x, y, true))
  48. }
  49. }
  50. }
  51. p.initNodeNeighbors()
  52. }
  53. func (p *AStar) initNodeNeighbors() {
  54. for x := range p.config.Width {
  55. for y := range p.config.Height {
  56. node := p.getNode(x, y)
  57. if node == nil || !node.isWalkable {
  58. continue
  59. }
  60. if node.IsEven() { // 偶数列
  61. // x + 1
  62. node.AddNeighbor(p.getNode(x+1, y), 14)
  63. node.AddNeighbor(p.getNode(x+1, y+1), 14)
  64. // x - 1
  65. node.AddNeighbor(p.getNode(x-1, y), 14)
  66. node.AddNeighbor(p.getNode(x-1, y+1), 14)
  67. // x
  68. node.AddNeighbor(p.getNode(x, y-1), 10)
  69. node.AddNeighbor(p.getNode(x, y+1), 10)
  70. } else { // 奇数列
  71. // x + 1
  72. node.AddNeighbor(p.getNode(x+1, y), 11)
  73. node.AddNeighbor(p.getNode(x+1, y-1), 11)
  74. // x - 1
  75. node.AddNeighbor(p.getNode(x-1, y), 11)
  76. node.AddNeighbor(p.getNode(x-1, y-1), 11)
  77. // x
  78. node.AddNeighbor(p.getNode(x, y-1), 10)
  79. node.AddNeighbor(p.getNode(x, y+1), 10)
  80. }
  81. }
  82. }
  83. }
  84. func (p *AStar) GetDistance(sx int32, sy int32, ex int32, ey int32) int32 {
  85. var (
  86. start = p.getNode(sx, sy)
  87. end = p.getNode(ex, ey)
  88. )
  89. if start == nil || end == nil {
  90. return math.MaxInt32
  91. }
  92. return p.calDistance(start, end)
  93. }
  94. func (p *AStar) ResetCache() {
  95. for _, node := range p.cache {
  96. node.isWalkable = true
  97. }
  98. }
  99. func (p *AStar) SetObstacles(x, y int32, isWalkable bool) {
  100. id := p.nodeID(x, y)
  101. node := p.cache[id]
  102. if node != nil {
  103. node.isWalkable = isWalkable
  104. }
  105. }
  106. // FindPath 寻路入口函数
  107. func (p *AStar) FindPath(x1, y1, x2, y2 int32) ([]*Node, bool) {
  108. var (
  109. start = p.getNode(x1, y1)
  110. end = p.getNode(x2, y2)
  111. )
  112. if !p.isWalkable(start) || !p.isWalkable(end) {
  113. clog.Debugf("path find failed: start or end is not walkable: (%v -> %v)", start, end)
  114. return nil, false
  115. }
  116. // 清理状态
  117. p.clear()
  118. // 将起点加入开启列表
  119. p.openList = append(p.openList, start)
  120. result := p.search(end)
  121. if len(result) == 0 {
  122. clog.Debugf("path find failed: find empty path: (%v -> %v)", start, end)
  123. return nil, false
  124. }
  125. return result, true
  126. }
  127. // search 寻路核心算法
  128. func (p *AStar) search(end *Node) []*Node {
  129. var (
  130. found bool
  131. current *Node
  132. )
  133. for len(p.openList) > 0 {
  134. current = p.currNode()
  135. // 找到终点
  136. if current.x == end.x && current.y == end.y {
  137. found = true
  138. break
  139. }
  140. for neighbor, cost := range current.neighbors {
  141. if p.closeList.Contains(neighbor) {
  142. continue
  143. }
  144. // 跳过不可行走的节点(可能在寻路过程中被动态设置为不可行走)
  145. if !p.isWalkable(neighbor) {
  146. continue
  147. }
  148. p.checkPath(neighbor, current, end, cost)
  149. }
  150. // 按F值排序开启列表
  151. p.sortOpenList()
  152. }
  153. if found {
  154. return p.buildPath(current)
  155. }
  156. return nil
  157. }
  158. func (p *AStar) currNode() *Node {
  159. node := p.openList[0]
  160. p.openList = p.openList[1:]
  161. p.closeList.Add(node)
  162. return node
  163. }
  164. func (p *AStar) clear() {
  165. p.openList = p.openList[:0]
  166. p.closeList = types.NewSet[*Node]()
  167. p.parentMap = make(map[*Node]*Node)
  168. p.gMap = make(map[*Node]int32)
  169. p.hMap = make(map[*Node]int32)
  170. }
  171. // isWalkable 检查节点是否可通行
  172. func (p *AStar) isWalkable(node *Node) bool {
  173. // // 出界
  174. // if p.outOfBounds(x, y) {
  175. // return false
  176. // }
  177. if node == nil || !node.isWalkable {
  178. return false
  179. }
  180. // 否则使用自定义函数
  181. if p.config.IsWalkableFunc != nil {
  182. return p.config.IsWalkableFunc(node.x, node.y)
  183. }
  184. return true
  185. }
  186. // func (p *AStar) outOfBounds(x, y int32) bool {
  187. // return x < p.config.Min.X ||
  188. // x >= p.config.Max.X ||
  189. // y < p.config.Min.Y ||
  190. // y >= p.config.Max.Y
  191. // }
  192. func (p *AStar) sortOpenList() {
  193. sort.Slice(p.openList, func(i, j int) bool {
  194. return p.getF(p.openList[i]) < p.getF(p.openList[j])
  195. })
  196. }
  197. func (p *AStar) getF(node *Node) int32 {
  198. return p.gMap[node] + p.hMap[node]
  199. }
  200. // checkPath 检查路径是否可行
  201. func (p *AStar) checkPath(node, parent, end *Node, cost int32) bool {
  202. // 检查是否在开启列表中
  203. if p.inOpenList(node) {
  204. if p.gMap[node]+cost < p.gMap[node] {
  205. p.parentMap[node] = parent
  206. {
  207. p.gMap[node] = p.calG(node, parent, cost)
  208. p.hMap[node] = p.calDistance(node, end)
  209. }
  210. }
  211. } else {
  212. p.parentMap[node] = parent
  213. {
  214. p.gMap[node] = p.calG(node, parent, cost)
  215. p.hMap[node] = p.calDistance(node, end)
  216. }
  217. p.openList = append(p.openList, node)
  218. }
  219. return true
  220. }
  221. // buildPath 构建路径
  222. func (p *AStar) buildPath(node *Node) []*Node {
  223. var result []*Node
  224. // 从终点回溯到起点
  225. for node != nil {
  226. result = append(result, node)
  227. node = p.parentMap[node]
  228. }
  229. // 翻转路径
  230. slices.Reverse(result)
  231. return result
  232. }
  233. // 辅助函数:检查节点是否在开启/关闭列表中
  234. func (p *AStar) inOpenList(node *Node) bool {
  235. return slices.Contains(p.openList, node)
  236. }
  237. func (p *AStar) ContainsNode(x, y int32) bool {
  238. return p.getNode(x, y) != nil
  239. }
  240. func (p *AStar) getNode(x, y int32) *Node {
  241. nodeID := p.nodeID(x, y)
  242. return p.cache[nodeID]
  243. }
  244. func (p *AStar) addNode(node *Node) {
  245. id := p.nodeID(node.x, node.y)
  246. p.cache[id] = node
  247. }
  248. func (p *AStar) nodeID(x, y int32) int32 {
  249. return y*p.config.Width + x
  250. }
  251. func (p *AStar) calDistance(node1, node2 *Node) int32 {
  252. dx := int32(math.Abs(float64(node1.x - node2.x)))
  253. dy := int32(math.Abs(float64(node1.y - node2.y)))
  254. return dx + dy
  255. }
  256. func (p *AStar) calG(node, parent *Node, cost int32) int32 {
  257. if parent == nil {
  258. return cost
  259. }
  260. var (
  261. grandpa = p.parentMap[parent]
  262. px = parent.x
  263. py = parent.y
  264. gx = parent.x
  265. gy = parent.y
  266. )
  267. if grandpa != nil {
  268. gx = grandpa.x
  269. gy = grandpa.y
  270. }
  271. var (
  272. pdx = px - gx
  273. pdy = py - gy
  274. dx = node.x - px
  275. dy = node.y - py
  276. turnPenlty = p.calTurnPenlty(dx, dy, pdx, pdy, node.IsOdd())
  277. )
  278. return p.gMap[parent] + cost + turnPenlty
  279. }
  280. func (p *AStar) calTurnPenlty(dx, dy, pdx, pdy int32, isOdd bool) int32 {
  281. if p.isTurn(dx, dy, pdx, pdy, isOdd) {
  282. return Cost_Turn
  283. }
  284. return 0
  285. }
  286. func (p *AStar) isTurn(dx, dy, pdx, pdy int32, isOdd bool) bool {
  287. value := dx * pdx
  288. if value == 0 {
  289. return false
  290. }
  291. if value > 0 {
  292. if isOdd {
  293. return pdy != dy+1
  294. }
  295. return pdy != dy-1
  296. }
  297. return true
  298. }
  299. func (p *AStar) Print(list []*Node) {
  300. if len(list) < 1 {
  301. return
  302. }
  303. var (
  304. fromNode = list[0]
  305. toNode = list[len(list)-1]
  306. )
  307. mapText := "\n"
  308. mapText += fmt.Sprintf(" Rectangle: [(%d,%d) -> (%d,%d)] \n", fromNode.x, fromNode.y, toNode.x, toNode.y)
  309. mapText += fmt.Sprintf(" Pathing: %v\n", list)
  310. mapText += " Pattern: start = ◉, point = ◦, end = ●\n"
  311. mapText += "Coordinate: row = x, col = y\n\n"
  312. for x := range p.config.Width {
  313. if x%2 == 0 {
  314. mapText += " "
  315. }
  316. for y := range p.config.Height {
  317. if slices.ContainsFunc(list, func(node *Node) bool { return node.x == x && node.y == y }) {
  318. if x == fromNode.x && y == fromNode.y {
  319. mapText += "◉ "
  320. } else if x == toNode.x && y == toNode.y {
  321. mapText += "● "
  322. } else {
  323. mapText += "◦ "
  324. }
  325. } else {
  326. mapText += fmt.Sprintf("%d ", p.config.MapData[x][y])
  327. }
  328. }
  329. mapText += "\n"
  330. }
  331. clog.Debug(mapText)
  332. }