astar_test.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. package astar6
  2. import (
  3. "f1-game/internal/types"
  4. "fmt"
  5. "testing"
  6. "time"
  7. )
  8. var (
  9. config1 = Config{
  10. Min: types.NewPoint(0, 0),
  11. Max: types.NewPoint(9, 29),
  12. MapData: [][]int32{
  13. // > Column or Height
  14. {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0}, // v Row ro Width
  15. {1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1},
  16. {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1},
  17. {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1},
  18. {1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1},
  19. {1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1},
  20. {1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1},
  21. {1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1},
  22. {1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1},
  23. {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1},
  24. },
  25. IsDebug: true,
  26. }
  27. config2 = Config{
  28. Min: types.NewPoint(0, 0),
  29. Max: types.NewPoint(9, 4),
  30. MapData: [][]int32{
  31. // > Column or Height
  32. {1, 1, 1, 1, 1}, // v Row ro Width
  33. {1, 0, 1, 0, 0},
  34. {1, 1, 0, 1, 1},
  35. {1, 1, 1, 0, 1},
  36. {1, 1, 1, 0, 1},
  37. {1, 1, 0, 0, 1},
  38. {1, 1, 0, 1, 1},
  39. {1, 1, 0, 1, 1},
  40. {1, 0, 1, 1, 1},
  41. {1, 1, 1, 1, 1},
  42. },
  43. IsDebug: true,
  44. }
  45. )
  46. func TestAstar(t *testing.T) {
  47. astar := New(config1)
  48. // astar := New(config2)
  49. startTime := time.Now().UnixNano()
  50. fmt.Printf("开始:%d\n", startTime)
  51. path, ok := astar.FindPath(DefaultFindContext(0, 0, 9, 29))
  52. // path, ok := astar.FindPath(0, 0, 9, 4)
  53. // path, ok := astar.FindPath(0, 0, 3, 8)
  54. endTime := time.Now().UnixNano()
  55. fmt.Printf("结束:%d\n", endTime)
  56. fmt.Printf("耗时:%v(ns)\n", endTime-startTime)
  57. if !ok {
  58. t.Error("find path failed")
  59. } else {
  60. astar.Print(path)
  61. }
  62. }
  63. // TestAstarVisualStraightPath 测试视觉上更直的路径
  64. // 验证从(1,1)到(7,2)的路径是否更接近视觉直线
  65. func TestAstarVisualStraightPath(t *testing.T) {
  66. config := Config{
  67. Min: types.NewPoint(0, 0),
  68. Max: types.NewPoint(9, 9),
  69. MapData: [][]int32{
  70. {1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
  71. {1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
  72. {1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
  73. {1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
  74. {1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
  75. {1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
  76. {1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
  77. {1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
  78. {1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
  79. {1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
  80. },
  81. IsDebug: true,
  82. }
  83. astar := New(config)
  84. // 测试从(1,1)到(7,2)的路径
  85. path, ok := astar.FindPath(DefaultFindContext(1, 1, 7, 2))
  86. if !ok {
  87. t.Error("find path failed")
  88. return
  89. }
  90. fmt.Println("\n=== 测试视觉直线路径 (1,1) -> (7,2) ===")
  91. fmt.Printf("路径: %v\n", path)
  92. // 打印路径节点
  93. for i, node := range path {
  94. fmt.Printf("步骤%d: (%d, %d)\n", i, node.X(), node.Y())
  95. }
  96. // 验证路径是否包含(5,3)点,这是视觉上更直的路径标志
  97. has53 := false
  98. for _, node := range path {
  99. if node.X() == 5 && node.Y() == 3 {
  100. has53 = true
  101. break
  102. }
  103. }
  104. if has53 {
  105. fmt.Println("\n✓ 路径包含(5,3)点,这是视觉上更直的路径!")
  106. } else {
  107. fmt.Println("\n路径不包含(5,3)点")
  108. }
  109. }
  110. // BenchmarkFindPath 性能基准测试
  111. func BenchmarkFindPath(b *testing.B) {
  112. astar := New(config1)
  113. b.ResetTimer()
  114. for i := 0; i < b.N; i++ {
  115. astar.FindPath(DefaultFindContext(0, 0, 9, 29))
  116. }
  117. }
  118. // BenchmarkFindPathParallel 并行性能测试
  119. func BenchmarkFindPathParallel(b *testing.B) {
  120. astar := New(config1)
  121. b.ResetTimer()
  122. b.RunParallel(func(pb *testing.PB) {
  123. for pb.Next() {
  124. astar.FindPath(DefaultFindContext(0, 0, 9, 29))
  125. }
  126. })
  127. }
  128. // TestConcurrentFindPath 并发安全测试
  129. func TestConcurrentFindPath(t *testing.T) {
  130. astar := New(config1)
  131. const goroutines = 100
  132. const iterations = 50
  133. done := make(chan bool, goroutines)
  134. for g := 0; g < goroutines; g++ {
  135. go func() {
  136. for i := 0; i < iterations; i++ {
  137. path, ok := astar.FindPath(DefaultFindContext(0, 0, 9, 29))
  138. if !ok {
  139. t.Error("concurrent find path failed")
  140. return
  141. }
  142. if len(path) == 0 {
  143. t.Error("concurrent find path returned empty")
  144. return
  145. }
  146. }
  147. done <- true
  148. }()
  149. }
  150. // 等待所有goroutine完成
  151. for g := 0; g < goroutines; g++ {
  152. <-done
  153. }
  154. fmt.Printf("並发测试完成: %d 个goroutine x %d 次迭代 = %d 次寻路\n", goroutines, iterations, goroutines*iterations)
  155. }
  156. // TestDynamicWalkableNode 测试动态设置节点为不可行走
  157. // 验证了 bug 修复:在寻路过程中,应诠节点的可行走状态
  158. func TestDynamicWalkableNode(t *testing.T) {
  159. config := Config{
  160. Min: types.NewPoint(0, 0),
  161. Max: types.NewPoint(9, 9),
  162. MapData: [][]int32{
  163. {1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
  164. {1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
  165. {1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
  166. {1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
  167. {1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
  168. {1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
  169. {1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
  170. {1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
  171. {1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
  172. {1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
  173. },
  174. IsDebug: false,
  175. }
  176. astar := New(config)
  177. // 第一次寻路:正常的直线路径
  178. path1, ok1 := astar.FindPath(DefaultFindContext(0, 0, 9, 9))
  179. if !ok1 {
  180. t.Error("First path finding failed")
  181. return
  182. }
  183. fmt.Printf("第一次寻路长度: %d\n", len(path1))
  184. // 动态设置中间节点(5,5)为不可行走
  185. middleNode := astar.GetNode(5, 5)
  186. if middleNode != nil {
  187. middleNode.SetWalkable(false)
  188. fmt.Println("已设置节点(5,5)为不可行走")
  189. }
  190. // 第二次寻路:与上一次不同(应诠中间节点)
  191. path2, ok2 := astar.FindPath(DefaultFindContext(0, 0, 9, 9))
  192. if !ok2 {
  193. t.Error("Second path finding failed after setting node as unwalkable")
  194. return
  195. }
  196. fmt.Printf("第二次寻路长度: %d\n", len(path2))
  197. // 验证路径有效:设置障碍后的路径应诠(5,5)节点
  198. for _, node := range path2 {
  199. if node.X() == 5 && node.Y() == 5 {
  200. t.Error("Path contains the unwalkable node (5,5), bug not fixed!")
  201. return
  202. }
  203. }
  204. fmt.Println("✓ 路径不包含不可行走节点(5,5),bug 修复正确!")
  205. // 恢复节点可行走状态
  206. if middleNode != nil {
  207. middleNode.SetWalkable(true)
  208. fmt.Println("已恢复节点(5,5)为可行走")
  209. }
  210. // 第三次寻路:应诠恢复为path1的类似正常路径
  211. path3, ok3 := astar.FindPath(DefaultFindContext(0, 0, 9, 9))
  212. if !ok3 {
  213. t.Error("Third path finding failed after restoring node as walkable")
  214. return
  215. }
  216. fmt.Printf("第三次寻路长度: %d\n", len(path3))
  217. }
  218. // BenchmarkFindPathWithObstacles 测试优化效果
  219. // 模拟实际游戏场景:1000x1000地图、从(2,1)到(98,97)、有障碍点
  220. func BenchmarkFindPathWithObstacles(b *testing.B) {
  221. // 创建1000x1000地图
  222. mapSize := int32(1000)
  223. mapData := make([][]int32, mapSize)
  224. for i := range mapData {
  225. mapData[i] = make([]int32, mapSize)
  226. for j := range mapData[i] {
  227. mapData[i][j] = 1
  228. }
  229. }
  230. // 随机放置1000个障碍点
  231. for i := 0; i < 1000; i++ {
  232. x := int32(i % 1000)
  233. y := int32(i / 1000)
  234. if x > 0 && x < mapSize && y > 0 && y < mapSize {
  235. mapData[x][y] = 0
  236. }
  237. }
  238. config := Config{
  239. Min: types.NewPoint(0, 0),
  240. Max: types.NewPoint(mapSize-1, mapSize-1),
  241. MapData: mapData,
  242. }
  243. astar := New(config)
  244. b.ResetTimer()
  245. for i := 0; i < b.N; i++ {
  246. // 从(2,1)到(98,97)的寻路
  247. astar.FindPath(DefaultFindContext(2, 1, 98, 97))
  248. }
  249. }
  250. // BenchmarkMemoryUsage 内存使用基准测试
  251. // 测试每次寻路的内存分配情况
  252. func BenchmarkMemoryUsage(b *testing.B) {
  253. astar := New(config1)
  254. b.ReportAllocs() // 报告内存分配
  255. b.ResetTimer()
  256. for i := 0; i < b.N; i++ {
  257. astar.FindPath(DefaultFindContext(0, 0, 9, 29))
  258. }
  259. }
  260. // BenchmarkShortPath 短距离寻路基准测试
  261. func BenchmarkShortPath(b *testing.B) {
  262. config := Config{
  263. Min: types.NewPoint(0, 0),
  264. Max: types.NewPoint(20, 20),
  265. MapData: [][]int32{
  266. {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
  267. {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
  268. {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
  269. {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
  270. {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
  271. {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
  272. {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
  273. {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
  274. {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
  275. {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
  276. {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
  277. {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
  278. {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
  279. {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
  280. {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
  281. {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
  282. {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
  283. {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
  284. {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
  285. {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
  286. {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
  287. },
  288. }
  289. astar := New(config)
  290. b.ResetTimer()
  291. for i := 0; i < b.N; i++ {
  292. astar.FindPath(DefaultFindContext(0, 0, 19, 19))
  293. }
  294. }
  295. // BenchmarkMediumPath 中等距离寻路基准测试
  296. func BenchmarkMediumPath(b *testing.B) {
  297. config := Config{
  298. Min: types.NewPoint(0, 0),
  299. Max: types.NewPoint(100, 100),
  300. MapData: make([][]int32, 101),
  301. }
  302. for i := range config.MapData {
  303. config.MapData[i] = make([]int32, 101)
  304. for j := range config.MapData[i] {
  305. config.MapData[i][j] = 1
  306. }
  307. }
  308. astar := New(config)
  309. b.ResetTimer()
  310. for i := 0; i < b.N; i++ {
  311. astar.FindPath(DefaultFindContext(0, 0, 99, 99))
  312. }
  313. }
  314. // BenchmarkLargePath 大型地图寻路基准测试
  315. func BenchmarkLargePath(b *testing.B) {
  316. config := Config{
  317. Min: types.NewPoint(0, 0),
  318. Max: types.NewPoint(500, 500),
  319. MapData: make([][]int32, 501),
  320. }
  321. for i := range config.MapData {
  322. config.MapData[i] = make([]int32, 501)
  323. for j := range config.MapData[i] {
  324. config.MapData[i][j] = 1
  325. }
  326. }
  327. astar := New(config)
  328. b.ResetTimer()
  329. for i := 0; i < b.N; i++ {
  330. astar.FindPath(DefaultFindContext(0, 0, 499, 499))
  331. }
  332. }