test_automove.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. """AutoMoveBehavior(5013 自动寻路)行为验证(不发真实网络 — 桩发送)。"""
  2. import time
  3. from typing import TYPE_CHECKING, cast
  4. from pbot.bot.automove import (
  5. AUTO_MOVE_ABORT,
  6. AUTO_MOVE_FINISH,
  7. AUTO_MOVE_START,
  8. AutoMoveBehavior,
  9. )
  10. from pbot.world.mirror import WorldMirror
  11. from tests.test_wander import SpyConn
  12. if TYPE_CHECKING:
  13. from pbot.bot.behavior import TickResult
  14. from pbot.transport.gateway import GatewayConnection
  15. def tick(b: AutoMoveBehavior, spy: SpyConn,
  16. world: WorldMirror) -> "TickResult":
  17. conn = cast("object", spy)
  18. return b.tick(cast("GatewayConnection", conn), world)
  19. def ready_world(x: int, y: int) -> WorldMirror:
  20. world = WorldMirror()
  21. # 用非映射图名: 本文件测帧布局机制(START/FINISH/ABORT/3013), 不测真实
  22. # 地形 — "等待地图"已映射 0139.map(46x50) 会加载真实墙, 坐标会越界
  23. world.me.map_name = "测试机制图"
  24. world.me.actor_key = 7
  25. world.me.x, world.me.y = x, y
  26. return world
  27. def make(spy: SpyConn, world: WorldMirror,
  28. dest: tuple[int, int]) -> AutoMoveBehavior:
  29. b = AutoMoveBehavior(sid=159, dest_x=dest[0], dest_y=dest[1])
  30. conn = cast("object", spy)
  31. b.on_enter(cast("GatewayConnection", conn), world)
  32. return b
  33. def test_start_notify_layout() -> None:
  34. """CS5013 START 帧布局: recog=X p1=Y p2=1 p3=sid(5013,1,x,y Lua 真源)。"""
  35. world = ready_world(23, 27)
  36. spy = SpyConn()
  37. b = make(spy, world, (40, 60))
  38. _ = tick(b, spy, world) # init → notify
  39. assert spy.sent == []
  40. _ = tick(b, spy, world) # notify → START + moving
  41. assert len(spy.sent) == 1
  42. msg_id, recog, p1, p2, p3, body = spy.sent[0]
  43. assert msg_id == 5013
  44. assert recog == 40
  45. assert p1 == 60
  46. assert p2 == AUTO_MOVE_START
  47. assert p3 == 159
  48. assert body == b""
  49. def test_arrive_sends_finish() -> None:
  50. """到达目标(切比雪夫≤2) → CS5013 act=3 FINISH → DONE。"""
  51. world = ready_world(38, 59)
  52. spy = SpyConn()
  53. b = make(spy, world, (40, 60))
  54. _ = tick(b, spy, world) # init → notify
  55. _ = tick(b, spy, world) # notify: START + 规划
  56. _ = tick(b, spy, world) # moving: 到达判定
  57. kinds = [s[0] for s in spy.sent]
  58. assert kinds[-1] == 5013
  59. finish = spy.sent[-1]
  60. assert finish[3] == AUTO_MOVE_FINISH # p2=3
  61. del b
  62. def test_abort_when_no_path() -> None:
  63. """目标被 2 格厚墙带完全隔开(A* 无路) → 不发 START, 直接 ABORT(2)。
  64. 注: A* 步长 2 格可越过 1 格薄墙(mid 检查只看中间格), 围墙须 ≥2 格厚。
  65. """
  66. world = ready_world(23, 27)
  67. spy = SpyConn()
  68. b = make(spy, world, (23, 45))
  69. for y in range(33, 38):
  70. for x in range(1024):
  71. b._grid.mark_blocked((x, y)) # pyright: ignore[reportPrivateUsage]
  72. _ = tick(b, spy, world) # init → notify
  73. _ = tick(b, spy, world) # notify: 规划失败
  74. kinds = [(s[0], s[3]) for s in spy.sent]
  75. assert kinds == [(5013, AUTO_MOVE_ABORT)]
  76. del b
  77. def test_moving_sends_3013_with_prediction() -> None:
  78. """移动中逐腿 CS3013, pre=预测落点(2 格), 位置前进。"""
  79. world = ready_world(23, 27)
  80. spy = SpyConn()
  81. b = make(spy, world, (23, 45))
  82. _ = tick(b, spy, world) # init
  83. _ = tick(b, spy, world) # notify(START)
  84. assert spy.sent[0][0] == 5013
  85. _ = tick(b, spy, world) # moving: 第一腿
  86. legs = [s for s in spy.sent if s[0] == 3013]
  87. assert len(legs) == 1
  88. _, recog, p1, p2, p3, body = legs[0]
  89. del recog, body
  90. assert p1 == 4 # 向下 dir=4
  91. assert p2 == (27 << 16) | 23 # 起点
  92. assert p3 == 159
  93. assert spy.pres[-1] == ((27 + 2) << 16) | 23 # pre=预测落点
  94. def test_pull_back_marks_blocked_and_replans() -> None:
  95. """SC28 弹回 → 禁行格记录 + 连续 2 次触发重规划(路径更新)。"""
  96. world = ready_world(23, 27)
  97. spy = SpyConn()
  98. b = make(spy, world, (23, 45))
  99. _ = tick(b, spy, world) # init
  100. _ = tick(b, spy, world) # notify
  101. _ = tick(b, spy, world) # 第一腿(dir=4)
  102. world.record("pull_back")
  103. b._last_step_t = time.time() - 2.0 # pyright: ignore[reportPrivateUsage]
  104. b._pending_t = time.time() - 2.0 # pyright: ignore[reportPrivateUsage]
  105. _ = tick(b, spy, world) # 第二腿(重规划后)
  106. world.record("pull_back")
  107. b._last_step_t = time.time() - 2.0 # pyright: ignore[reportPrivateUsage]
  108. b._pending_t = time.time() - 2.0 # pyright: ignore[reportPrivateUsage]
  109. _ = tick(b, spy, world) # 重规划触发
  110. blocked = (23, 27 + 2) # dir=4 弹回落点: me 未推进仍 (23,27) + 2 格
  111. assert blocked in b._grid.blocked # pyright: ignore[reportPrivateUsage]
  112. legs = [s for s in spy.sent if s[0] == 3013]
  113. assert len(legs) >= 2
  114. del b
  115. def test_server_auto_move_event_consumed() -> None:
  116. """SC5013 下行事件(server_auto_move)可被消费且不崩溃。"""
  117. world = ready_world(23, 27)
  118. spy = SpyConn()
  119. b = make(spy, world, (23, 45))
  120. world.record("server_auto_move", "(30,30)")
  121. _ = tick(b, spy, world)
  122. assert len(world.events) == 0 # 事件已清空
  123. del b
  124. def test_waits_until_world_known() -> None:
  125. """SC50/SC51 未就绪 → 不发帧。"""
  126. world = WorldMirror()
  127. spy = SpyConn()
  128. b = make(spy, world, (40, 60))
  129. _ = tick(b, spy, world)
  130. assert spy.sent == []
  131. del b