dsh-eval-guard 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (224) hide show
  1. package/README.md +91 -13
  2. package/assets/bomberman/workspace.zip +0 -0
  3. package/assets/commit0/instance.json +30 -0
  4. package/assets/commit0/workspace.zip +0 -0
  5. package/assets/ctf/workspace.zip +0 -0
  6. package/assets/featurebench/hidden/test_regression.py +690 -0
  7. package/assets/featurebench/instance.json +16 -0
  8. package/assets/featurebench/workspace.zip +0 -0
  9. package/assets/swe-bench-pro/hidden/test.patch +36 -0
  10. package/assets/swe-bench-pro/instance.json +10 -0
  11. package/assets/swe-bench-pro/workspace.zip +0 -0
  12. package/assets/tank/workspace.zip +0 -0
  13. package/eval/README.md +18 -0
  14. package/eval/arena/arena/README.md +21 -0
  15. package/eval/arena/arena/bt.py +104 -0
  16. package/eval/arena/arena/clock.py +15 -0
  17. package/eval/arena/arena/core.py +265 -0
  18. package/eval/arena/arena/run.cmd +24 -0
  19. package/eval/arena/arena/run.sh +11 -0
  20. package/eval/arena/arena/schedule.py +79 -0
  21. package/eval/arena/arena/tournament.py +183 -0
  22. package/eval/arena/bomberman/bots/README.md +16 -0
  23. package/eval/arena/bomberman/bots/deepseek-v4.1-flash.cpp +447 -0
  24. package/eval/arena/bomberman/bots/doubao-seed-2.1.cpp +542 -0
  25. package/eval/arena/bomberman/bots/glm-5.3.cpp +406 -0
  26. package/eval/arena/bomberman/bots/gpt-6-astra.cpp +768 -0
  27. package/eval/arena/bomberman/bots/gpt-6-sol.cpp +444 -0
  28. package/eval/arena/bomberman/bots/kimi-k3.cpp +432 -0
  29. package/eval/arena/bomberman/bots/mimo-v2.6-pro.cpp +529 -0
  30. package/eval/arena/bomberman/bots/opus-5.5.cpp +548 -0
  31. package/eval/arena/bomberman/bots/qwen-3.8-max.cpp +836 -0
  32. package/eval/arena/bomberman/tournament/README.md +11 -0
  33. package/eval/arena/bomberman/tournament/bt.py +56 -0
  34. package/eval/arena/bomberman/tournament/evaluate.py +199 -0
  35. package/eval/arena/bomberman/tournament/run.py +79 -0
  36. package/eval/arena/bomberman/tournament/schedule-2to10.json.gz +0 -0
  37. package/eval/arena/bomberman/workspace/MANIFEST.json +13 -0
  38. package/eval/arena/bomberman/workspace/README.md +29 -0
  39. package/eval/arena/bomberman/workspace/RULES.md +100 -0
  40. package/eval/arena/bomberman/workspace/baseline.cpp +240 -0
  41. package/eval/arena/bomberman/workspace/clock.py +25 -0
  42. package/eval/arena/bomberman/workspace/engine/arena.py +198 -0
  43. package/eval/arena/bomberman/workspace/engine/build.py +35 -0
  44. package/eval/arena/bomberman/workspace/engine/engine.py +256 -0
  45. package/eval/arena/bomberman/workspace/engine/verify_replay.py +24 -0
  46. package/eval/arena/bomberman/workspace/run.cmd +28 -0
  47. package/eval/arena/bomberman/workspace/run.sh +16 -0
  48. package/eval/arena/bomberman/workspace/viewer/index.html +42 -0
  49. package/eval/arena/ctf/bots/README.md +18 -0
  50. package/eval/arena/ctf/bots/deepseek-v4.1-flash.cpp +601 -0
  51. package/eval/arena/ctf/bots/doubao-seed-2.1-turbo.cpp +320 -0
  52. package/eval/arena/ctf/bots/fable-5.1.cpp +634 -0
  53. package/eval/arena/ctf/bots/glm-5.3.cpp +689 -0
  54. package/eval/arena/ctf/bots/gpt-6-astra.cpp +657 -0
  55. package/eval/arena/ctf/bots/gpt-6.1-sol.cpp +725 -0
  56. package/eval/arena/ctf/bots/kimi-k3.cpp +667 -0
  57. package/eval/arena/ctf/bots/mimo-v2.6-pro.cpp +533 -0
  58. package/eval/arena/ctf/bots/opus-5.5.cpp +645 -0
  59. package/eval/arena/ctf/bots/qwen-3.8-max.cpp +726 -0
  60. package/eval/arena/ctf/bots/sonnet-5.5.cpp +569 -0
  61. package/eval/arena/ctf/tournament/README.md +15 -0
  62. package/eval/arena/ctf/tournament/formal-2to8.json +31 -0
  63. package/eval/arena/ctf/tournament/formal-9to15.json +31 -0
  64. package/eval/arena/ctf/workspace/README.md +41 -0
  65. package/eval/arena/ctf/workspace/RULES.md +119 -0
  66. package/eval/arena/ctf/workspace/arena.py +186 -0
  67. package/eval/arena/ctf/workspace/baseline.cpp +257 -0
  68. package/eval/arena/ctf/workspace/clock.py +15 -0
  69. package/eval/arena/ctf/workspace/core.py +265 -0
  70. package/eval/arena/ctf/workspace/engine.py +389 -0
  71. package/eval/arena/ctf/workspace/run.cmd +24 -0
  72. package/eval/arena/ctf/workspace/run.sh +11 -0
  73. package/eval/arena/ctf/workspace/viewer.html +71 -0
  74. package/eval/arena/land/bots/README.md +17 -0
  75. package/eval/arena/land/bots/deepseek-4.1f@workbuddy#1/bot.cpp +608 -0
  76. package/eval/arena/land/bots/glm-5.3@workbuddy#1/bot.cpp +521 -0
  77. package/eval/arena/land/bots/gpt-6-astra@codex#1/bot.cpp +373 -0
  78. package/eval/arena/land/bots/gpt-6-sol@codex#1/bot.cpp +341 -0
  79. package/eval/arena/land/bots/kimi-k3@workbuddy#1/bot.cpp +492 -0
  80. package/eval/arena/land/bots/mimo-2.6-pro@zcode#1/bot.cpp +652 -0
  81. package/eval/arena/land/bots/mystery-model@zcode#1/bot.cpp +466 -0
  82. package/eval/arena/land/bots/opus-5.5@claude-code#1/bot.cpp +630 -0
  83. package/eval/arena/land/bots/sonnet-5@claude-code#1/bot.cpp +619 -0
  84. package/eval/arena/land/tournament/make_replay_page.py +17 -0
  85. package/eval/arena/land/tournament/rate.py +290 -0
  86. package/eval/arena/land/tournament/round_robin.py +172 -0
  87. package/eval/arena/land/tournament/sparring.cpp +356 -0
  88. package/eval/arena/land/workspace/RULES.md +114 -0
  89. package/eval/arena/land/workspace/TASK.md +46 -0
  90. package/eval/arena/land/workspace/anchors/.build/greedy_f2e6d2dea5ca +0 -0
  91. package/eval/arena/land/workspace/anchors/greedy.cpp +194 -0
  92. package/eval/arena/land/workspace/anchors/random.cpp +80 -0
  93. package/eval/arena/land/workspace/clock.py +64 -0
  94. package/eval/arena/land/workspace/engine/build.py +33 -0
  95. package/eval/arena/land/workspace/engine/game.py +273 -0
  96. package/eval/arena/land/workspace/engine/referee.py +308 -0
  97. package/eval/arena/land/workspace/starter/.build/bot_be32b45b4699 +0 -0
  98. package/eval/arena/land/workspace/starter/bot.cpp +82 -0
  99. package/eval/arena/land/workspace/viewer/viewer.html +210 -0
  100. package/eval/arena/tank/bots/README.md +24 -0
  101. package/eval/arena/tank/bots/half1/deepseek-v4.1-flash.cpp +367 -0
  102. package/eval/arena/tank/bots/half1/doubao-seed-evolving.cpp +402 -0
  103. package/eval/arena/tank/bots/half1/gemini-4-argon.cpp +1132 -0
  104. package/eval/arena/tank/bots/half1/glm-5.3.cpp +551 -0
  105. package/eval/arena/tank/bots/half1/gpt-6-astra.cpp +233 -0
  106. package/eval/arena/tank/bots/half1/gpt-6.1-sol-ultrafast.cpp +350 -0
  107. package/eval/arena/tank/bots/half1/gpt-6.1-sol.cpp +78 -0
  108. package/eval/arena/tank/bots/half1/grok-4.7.cpp +1099 -0
  109. package/eval/arena/tank/bots/half1/haiku-5.5.cpp +210 -0
  110. package/eval/arena/tank/bots/half1/kimi-k3.cpp +353 -0
  111. package/eval/arena/tank/bots/half1/mimo-v2.6-pro.cpp +336 -0
  112. package/eval/arena/tank/bots/half1/muse-wonder-full.cpp +560 -0
  113. package/eval/arena/tank/bots/half1/opus-5.5.cpp +435 -0
  114. package/eval/arena/tank/bots/half1/qwen-3.8-max.cpp +537 -0
  115. package/eval/arena/tank/bots/half2/deepseek-v4.1-flash.cpp +425 -0
  116. package/eval/arena/tank/bots/half2/doubao-seed-evolving.cpp +308 -0
  117. package/eval/arena/tank/bots/half2/gemini-4-argon.cpp +1390 -0
  118. package/eval/arena/tank/bots/half2/glm-5.3.cpp +651 -0
  119. package/eval/arena/tank/bots/half2/gpt-6-astra.cpp +241 -0
  120. package/eval/arena/tank/bots/half2/gpt-6.1-sol-ultrafast.cpp +356 -0
  121. package/eval/arena/tank/bots/half2/gpt-6.1-sol.cpp +87 -0
  122. package/eval/arena/tank/bots/half2/grok-4.7.cpp +1205 -0
  123. package/eval/arena/tank/bots/half2/haiku-5.5.cpp +210 -0
  124. package/eval/arena/tank/bots/half2/kimi-k3.cpp +487 -0
  125. package/eval/arena/tank/bots/half2/mimo-v2.6-pro.cpp +336 -0
  126. package/eval/arena/tank/bots/half2/muse-wonder-full.cpp +560 -0
  127. package/eval/arena/tank/bots/half2/opus-5.5.cpp +476 -0
  128. package/eval/arena/tank/bots/half2/qwen-3.8-max.cpp +392 -0
  129. package/eval/arena/tank/tournament/README.md +24 -0
  130. package/eval/arena/tank/tournament/bt.py +218 -0
  131. package/eval/arena/tank/tournament/final.json +202 -0
  132. package/eval/arena/tank/tournament/halftime.json +146 -0
  133. package/eval/arena/tank/tournament/progress.json +911 -0
  134. package/eval/arena/tank/tournament/schedule.py +100 -0
  135. package/eval/arena/tank/tournament/sparring.cpp +928 -0
  136. package/eval/arena/tank/tournament/tournament.py +1426 -0
  137. package/eval/arena/tank/workspace/README.md +169 -0
  138. package/eval/arena/tank/workspace/RULES.md +443 -0
  139. package/eval/arena/tank/workspace/arena.py +862 -0
  140. package/eval/arena/tank/workspace/baseline.cpp +259 -0
  141. package/eval/arena/tank/workspace/clock.py +103 -0
  142. package/eval/arena/tank/workspace/core.py +712 -0
  143. package/eval/arena/tank/workspace/engine.py +573 -0
  144. package/eval/arena/tank/workspace/history/.lock +0 -0
  145. package/eval/arena/tank/workspace/history/20261017-193610-08715287f91f-bot.cpp +109 -0
  146. package/eval/arena/tank/workspace/history/20261017-193619-f00b2d5635fa-gpt-6-astra.cpp +241 -0
  147. package/eval/arena/tank/workspace/history/20261017-193643-63319e0a2b1a-gpt-6_1-sol-ultrafast.cpp +356 -0
  148. package/eval/arena/tank/workspace/history/20261017-193647-41c1341d0a8a-gemini-4-argon.cpp +1390 -0
  149. package/eval/arena/tank/workspace/history/20261017-193652-e34c0f45539a-gpt-6_1-sol.cpp +87 -0
  150. package/eval/arena/tank/workspace/history/20261017-193654-c026348ce96e-opus-5_5.cpp +476 -0
  151. package/eval/arena/tank/workspace/history/20261017-193655-4a21366e94d1-muse-wonder-full.cpp +560 -0
  152. package/eval/arena/tank/workspace/history/20261017-193657-dbc4e9969c52-mimo-v2_6-pro.cpp +336 -0
  153. package/eval/arena/tank/workspace/history/20261017-193703-723cd036eb31-grok-4_7.cpp +1205 -0
  154. package/eval/arena/tank/workspace/history/20261017-193707-1ebbb227be68-deepseek-v4_1-flash.cpp +425 -0
  155. package/eval/arena/tank/workspace/history/20261017-193709-aeb0e7202ad5-doubao-seed-evolving.cpp +308 -0
  156. package/eval/arena/tank/workspace/history/20261017-193710-b21ebeb9bc76-kimi-k3.cpp +487 -0
  157. package/eval/arena/tank/workspace/history/20261017-193712-54cd86531905-haiku-5_5.cpp +210 -0
  158. package/eval/arena/tank/workspace/history/20261017-193714-efdb8e35c0bf-qwen-3_8-max.cpp +392 -0
  159. package/eval/arena/tank/workspace/history/20261017-193715-681423c23fdb-glm-5_3.cpp +651 -0
  160. package/eval/arena/tank/workspace/history/20261017-193717-1a8b3c75181d-sparring.cpp +928 -0
  161. package/eval/arena/tank/workspace/history/20261017-193720-1e0803a541e8-baseline.cpp +259 -0
  162. package/eval/arena/tank/workspace/history/log.jsonl +103 -0
  163. package/eval/arena/tank/workspace/judge/cycles.hpp +53 -0
  164. package/eval/arena/tank/workspace/judge/fast.cpp +337 -0
  165. package/eval/arena/tank/workspace/judge/referee.cpp +125 -0
  166. package/eval/arena/tank/workspace/judge/speed.cpp +99 -0
  167. package/eval/arena/tank/workspace/run.cmd +31 -0
  168. package/eval/arena/tank/workspace/run.sh +23 -0
  169. package/eval/arena/tank/workspace/tank.hpp +1762 -0
  170. package/eval/arena/tank/workspace/tank_main.cpp +215 -0
  171. package/eval/arena/tank/workspace/viewer.html +1150 -0
  172. package/eval/arena/warehouse/workspace/SPEC.md +130 -0
  173. package/eval/arena/warehouse/workspace/TASK.md +71 -0
  174. package/eval/arena/warehouse/workspace/baseline/common.h +88 -0
  175. package/eval/arena/warehouse/workspace/baseline/main.cpp +27 -0
  176. package/eval/arena/warehouse/workspace/baseline/old_stuff.cpp +87 -0
  177. package/eval/arena/warehouse/workspace/baseline/sim.cpp +427 -0
  178. package/eval/arena/warehouse/workspace/baseline/util.cpp +220 -0
  179. package/eval/arena/warehouse/workspace/clock.py +42 -0
  180. package/eval/arena/warehouse/workspace/src/common.h +88 -0
  181. package/eval/arena/warehouse/workspace/src/main.cpp +27 -0
  182. package/eval/arena/warehouse/workspace/src/old_stuff.cpp +87 -0
  183. package/eval/arena/warehouse/workspace/src/sim.cpp +427 -0
  184. package/eval/arena/warehouse/workspace/src/util.cpp +220 -0
  185. package/eval/arena/warehouse/workspace/tools/bench.py +119 -0
  186. package/eval/arena/warehouse/workspace/tools/check_input.py +161 -0
  187. package/eval/arena/warehouse/workspace/tools/gen.py +132 -0
  188. package/eval/arena/warehouse/workspace/tools/measure.py +107 -0
  189. package/eval/arena/warehouse/workspace/workloads/public/pub_crowded.in +1420 -0
  190. package/eval/arena/warehouse/workspace/workloads/public/pub_crowded.out +9162 -0
  191. package/eval/arena/warehouse/workspace/workloads/public/pub_dynamic.in +1465 -0
  192. package/eval/arena/warehouse/workspace/workloads/public/pub_dynamic.out +3439 -0
  193. package/eval/arena/warehouse/workspace/workloads/public/pub_flood.in +2757 -0
  194. package/eval/arena/warehouse/workspace/workloads/public/pub_flood.out +3126 -0
  195. package/eval/arena/warehouse/workspace/workloads/public/pub_small.in +731 -0
  196. package/eval/arena/warehouse/workspace/workloads/public/pub_small.out +2578 -0
  197. package/eval/arena/warehouse/workspace/workloads/public/pub_wide.in +528 -0
  198. package/eval/arena/warehouse/workspace/workloads/public/pub_wide.out +2026 -0
  199. package/eval/bomberman.py +20 -0
  200. package/eval/commit0.py +21 -0
  201. package/eval/common.py +61 -0
  202. package/eval/ctf.py +20 -0
  203. package/eval/featurebench.py +25 -0
  204. package/eval/land.py +20 -0
  205. package/eval/legacy/bomberman-prices.json +4 -0
  206. package/eval/legacy/bomberman.py +161 -0
  207. package/eval/legacy/commit0.py +91 -0
  208. package/eval/legacy/ctf-prices.json +4 -0
  209. package/eval/legacy/ctf.py +178 -0
  210. package/eval/legacy/featurebench.py +103 -0
  211. package/eval/legacy/land-prices.json +4 -0
  212. package/eval/legacy/land.py +177 -0
  213. package/eval/legacy/swe_bench_pro.py +116 -0
  214. package/eval/legacy/tank-prices.json +3 -0
  215. package/eval/legacy/tank.py +408 -0
  216. package/eval/legacy/warehouse-prices.json +4 -0
  217. package/eval/legacy/warehouse.py +365 -0
  218. package/eval/swe-bench-pro.py +25 -0
  219. package/eval/tank.py +20 -0
  220. package/eval/warehouse.py +20 -0
  221. package/lib/client/index.js +376 -63
  222. package/lib/client/index.js.map +1 -1
  223. package/lib/index.js +684 -27
  224. package/package.json +2 -1
@@ -0,0 +1,169 @@
1
+ # 坦克大战
2
+
3
+ 写一个 bot:`submission/bot.cpp`。1 对 1,每方三辆不同车型的坦克守一个基地。完整规则见 `RULES.md`(开头有一屏速览),规则的精确实现是 `tank.hpp`(裁判用的就是这一份,两者不一致时以 `tank.hpp` 为准)。
4
+
5
+ 比赛分上下半场,解压时开始计时。时间规则和考试一样:每个半场 30 分钟,最早交卷时间是第 20 分钟,最晚到 45 分钟强制收卷。最终只看 bot 的强度,提前交卷没有任何好处。上半场结束后所有选手的 bot 互相打一轮,然后你会收到中场包(见"中场"一节),再用下半场继续改进。
6
+
7
+ **交什么**:只交 `submission/bot.cpp` 这一个文件,每个半场结束时它的内容就是你那个半场的版本;另外用几句话说明你的策略。
8
+
9
+ ## 怎么写 bot
10
+
11
+ `bot.cpp` 只需要定义两个函数,两个都必须有:
12
+
13
+ ```cpp
14
+ #include "tank.hpp"
15
+
16
+ // 每局开始调用一次。s 是开局局面,me 是你的座位(0 在下方,1 在上方)。
17
+ void init(const tank::State& s, int me) {}
18
+
19
+ // 每回合调用一次,返回三辆车(快速、火力、装甲)这一回合的命令。
20
+ tank::Orders decide(const tank::State& s, int me) {
21
+ tank::Orders orders; // 默认三辆都不动
22
+ for (int type = 0; type < 3; ++type) {
23
+ const int i = tank::tank_id(me, type); // 这辆车的编号
24
+ tank::Cmd list[tank::N_CMDS];
25
+ const int n = tank::legal_commands(s, i, list); // 这辆车现在有意义的命令,第一条总是"不动"
26
+ orders.cmd[type] = list[n - 1];
27
+ }
28
+ return orders;
29
+ }
30
+ ```
31
+
32
+ - 不用写任何读输入、写输出的代码。局面直接以 `tank::State` 传进来;想往前推演,把它拷一份再调 `tank::step`。
33
+ - 全局变量、静态变量可以跨回合保留。每一局开始时它们都是初始值。
34
+ - 调试信息写到标准错误(`fprintf(stderr, ...)` 或 `std::cerr`)。`play` 会显示最后几行,全部内容按回合存在回放里。写到标准输出的内容也会并进调试输出,不会干扰对局。
35
+ - 除了 `tank.hpp` 和标准库不能依赖别的文件。想改引擎的行为,就把需要的代码抄进 `bot.cpp` 里改。可以用 `<windows.h>`,放在 `tank.hpp` 前面或后面都行。
36
+ - 返回的命令不合法(比如给非快速型下了两步)时,这一回合三辆车都不动,并记一次失败。
37
+
38
+ ### 思考预算
39
+
40
+ - 首回合(`init` 加第一次 `decide`,含程序启动)2000 CPU 毫秒,之后每回合 50 CPU 毫秒。"CPU 毫秒"是**你的程序自己消耗的 CPU 时间**,被挂起、等输入的时间都不算。
41
+ - `tank::budget_left()` 返回本回合还剩多少 CPU 毫秒(浮点数),用它决定什么时候收手:`while (tank::budget_left() > 3) { /* 再搜一层 */ }`。裁判从外面量你的整个进程,比它看到的多零点几毫秒,留 2–3 毫秒余量就够。
42
+ - 超出预算的回合三辆车都不动,记一次失败;**连续 3 次失败判负**(超预算和不合法的命令合在一起数)。一回合用掉 150 CPU 毫秒以上就是连续三次。程序崩溃或退出直接判负。
43
+ - 可以开线程,但预算按所有线程合计,`budget_left()` 只看得到调用它的那个线程。
44
+ - 另有墙上时钟的保险上限:一回合 2 秒(首回合 4 秒)没有回复,记一次失败。
45
+
46
+ ## 编译和对战
47
+
48
+ 不用自己编译:下面的命令遇到 `.cpp` 会自动编译(结果缓存在 `build/`,源码没变不重编,编译出错会显示编译器的报错)。在本目录运行;这是 PowerShell 和命令提示符的写法,在 bash 里把 `.\run.cmd` 换成 `sh run.sh`。
49
+
50
+ ```text
51
+ .\run.cmd clock.py
52
+ .\run.cmd arena.py check
53
+ .\run.cmd arena.py play submission/bot.cpp submission/bot.cpp --seed 7 --out first
54
+ .\run.cmd arena.py show replays/first.json 20 --around 2
55
+ .\run.cmd arena.py fast submission/bot.cpp submission/bot.cpp --games 200
56
+ .\run.cmd arena.py batch submission/bot.cpp submission/bot.cpp --games 20
57
+ .\run.cmd arena.py vs submission/bot.cpp submission/bot.cpp --games 200
58
+ .\run.cmd arena.py expand replays/first.json
59
+ .\run.cmd arena.py html replays/first.json
60
+ ```
61
+
62
+ 参与对战的可以是 `.cpp` 源码,也可以是编译好的程序;同一个程序可以两边都上。
63
+
64
+ | 命令 | 做什么 |
65
+ |---|---|
66
+ | `clock.py` | 看时间:显示形如"上半场 已用 12.3 分钟。最早交卷时间是第 20 分钟(还有 7.7 分钟),30 分钟是本半场的时间,45 分钟强制收卷";中场包解压后改为显示下半场 |
67
+ | `check` | 检查环境:Python、编译器、`submission/bot.cpp` 能否编译并打完一局 |
68
+ | `play A B` | 正式模式打一局,A 坐 0 号位、B 坐 1 号位。打印结果、每方的预算用量、故障、调试输出的最后几行;回放存到 `replays/名字.json`,同时生成网页版 `replays/名字.html`。`--seed` 选地图,`--style` 选风格(`auto` 按种子轮换,或 `open`、`brick`、`water`、`steel`) |
69
+ | `show 回放 回合` | 在终端里看这一回合结算后的局面:字符画棋盘、每辆车的状态、子弹、命令、事件、调试输出。`--around 2` 连前后各两回合一起看 |
70
+ | `fast A B` | 快速模式批量。只支持源码 |
71
+ | `batch A B` | 正式模式批量 |
72
+ | `vs 新版 旧版` | 比较两个版本。两边都是源码时用快速模式,否则用正式模式;`--mode fast` / `--mode formal` 可以指定 |
73
+ | `expand 回放` | 把回放展开成逐回合的完整局面和事件,存成 `回放名.frames.json`,方便自己写脚本分析 |
74
+ | `html 回放` | 把已有的回放(比如中场包里的)做成网页,存成 `回放名.html` |
75
+
76
+ 批量命令(`fast`、`batch`、`vs`)每张图换边各打一局:`--games 200` 就是 100 张图,种子从 `--seed`(默认 1)起连续取,`--workers` 指定并行数。**输出的最后一行是一行汇总**(谁对谁、胜-平-负、得分率 ± 误差范围、局数、有无故障),只想要结论就取最后一行。想重看某一局,用 `play` 加同一个种子、按同样的座次再打一遍。
77
+
78
+ ### 正式模式和快速模式
79
+
80
+ | | 正式模式(`play`、`batch`) | 快速模式(`fast`) |
81
+ |---|---|---|
82
+ | 怎么跑 | 和正式比赛完全一样:每个 bot 是一个独立的程序 | 两个 bot 装进同一个程序里直接互相调用 |
83
+ | 速度 | 简单的 bot 每秒十几局(每个并行位) | 简单的 bot 每秒几百局(每个并行位) |
84
+ | 思考预算 | 超了判罚 | 只统计、不判罚 |
85
+ | 出错 | 崩溃、超预算、不合法的命令照常判罚并记录 | 不合法的命令照常判罚;任何一方崩溃、卡死、抛异常,整批停下,并报告是谁、哪一局 |
86
+ | 调试输出 | 存进回放 | 不显示 |
87
+ | 参与者 | 源码或编译好的程序 | 只能是源码 |
88
+
89
+ ### 结果能不能复现
90
+
91
+ 对局里没有随机,所以只看你的 bot 怎么决定停止搜索:
92
+
93
+ - **不看时间的 bot**(固定深度、固定节点数,或者根本不搜索):结果完全确定。同一张图、同样的两个程序、同样的座次,快速模式每次打出的都是同一局,并且和正式模式逐回合相同(只要正式模式下没有超预算);并行多少、机器忙不忙都不影响。这种 bot 放心用快速模式做大批量对比。
94
+ - **按 `budget_left()` 停的 bot**:同样的 CPU 时间里能搜多少取决于机器当时的状态,所以同一个版本在单局和批量之间、快速和正式之间、并行少和并行多之间,都可能打出不同的对局。并行越多差得越多。两次对比的差别里会混进这种波动。
95
+
96
+ **推荐做法:给搜索留一个"固定搜索量"的开关,对比实验时打开,提交时按预算停。** 例如:
97
+
98
+ ```cpp
99
+ #define FIXED_NODES 0 // 对比实验:改成固定的节点数(比如 20000);提交:保持 0,按预算停
100
+ static bool keep_searching(long nodes) {
101
+ return FIXED_NODES ? nodes < FIXED_NODES : tank::budget_left() > 3;
102
+ }
103
+ ```
104
+
105
+ 把要比的两个版本各存成一个 `.cpp`,开关设成同一个固定值,用 `vs`(快速模式)比,结果可以重复。定下版本后把开关改回 0 放进 `submission/bot.cpp`,再用正式模式(`play`,或并行数不大的 `batch`)确认没有超预算、没有崩溃;是否接近 50 CPU 毫秒的时限,以单局 `play` 的用量为准。
106
+
107
+ ### `vs` 怎么读
108
+
109
+ 输出形如下面这样。**这里的数字是为了说明格式编的,不是任何真实对局的结果**:
110
+
111
+ ```text
112
+ tank-cpp-0.1 快速模式 new 对 old 200 局(100 张图,种子 1–100,每张图换边各打一局) 并行 8 用时 1.2 秒
113
+ new:胜 96 平 20 负 84 得分率 53.0% ± 4.1%(95% 区间,按图计)
114
+ new 坐 0 号位:胜 50 平 9 负 41 坐 1 号位:胜 46 平 11 负 43
115
+ 100 张图里有 37 张换边后的两局逐回合完全相同(两个程序在这些图上的表现没有任何差别),能看出差别的有 63 张
116
+ ……(结束方式、双方的用量)
117
+ 汇总:new 对 old 96-20-84(胜-平-负) 得分率 53.0% ± 4.1% 200 局 故障:无
118
+ ```
119
+
120
+ - 得分率:胜算 1、平算 0.5,站在第一个程序(新版)这边。误差范围是 95% 区间,按图计算(一张图上换边的两局算一个样本)。得分率减去误差范围还高于 50%,才能说新版更强;否则加局数再看。
121
+ - "完全相同"的图不提供信息:改动很小的两个版本会在很多图上做出完全一样的动作,这样的图上换边的两局是同一局。能看出差别的图很少时,得分率接近 50% 不代表两个版本一样强,换一批种子(`--seed 1001`)或加局数。
122
+ - 两个座位的胜负分开列出:如果新版只在一个座位上赢得多,多半是地图或车型排布的缘故,不是版本的差别。
123
+
124
+ ## 可以做和不可以做的
125
+
126
+ - **本地可以并行跑任意多的对战**:`--workers` 开多大、同时开几个 `arena.py` 命令都可以(并行多了每局变慢,按预算停的 bot 搜得更浅)。
127
+ - **中场包里的对手程序可以当作练习对手,也可以当作自动调参的对象**:批量对战、写脚本循环调参数都可以。
128
+ - **不得反编译对手的程序。** 它们只用来对战。
129
+
130
+ ## 看回放、找回旧版本
131
+
132
+ - `play` 生成的 `replays/名字.html`(或 `html` 命令生成的网页)用浏览器打开就能看,不需要联网:带动画的棋盘,两侧是每辆车的状态和命令、事件、思考用量、故障和你的调试输出。空格播放 / 暂停,左右方向键逐回合,`[` `]` 跳到上一个 / 下一个关键事件,按 `H` 看全部快捷键;文件名后面加 `?turn=37` 直接打开到第 37 回合。也可以打开 `viewer.html`,把 `expand` 生成的 `*.frames.json` 拖进去。
133
+ - 想用程序分析对局,读 `expand` 生成的 JSON;想知道某一回合的精确数值,用 `show`。
134
+ - 版本存档:每次运行 `check`、`play`、`batch`、`fast`、`vs`,参与的每份源码都会按内容存一份到 `history/`(文件名带时间和内容哈希,同样的内容只存一次),并在 `history/log.jsonl` 里追加一行:时间、命令、参与的版本、结果摘要。想找回之前的版本、回头看谁对谁打成什么样,去那里找。请不要删除 `history/`。
135
+
136
+ ## 中场
137
+
138
+ 所有选手的上半场版本互相打一轮之后,你会收到中场包,解压后在 `workspace/halftime/` 里:
139
+
140
+ - 上半场的成绩。
141
+ - 你自己的对局回放(和本地 `play` 存的是同一种格式,可以直接用 `show` 和 `expand`)。
142
+ - 其他选手上半场的程序:`halftime/opponents/` 下的可执行文件,没有源码,所以只能用正式模式(`play`、`batch`、`vs --mode formal`)。例如(把文件名换成实际的):
143
+
144
+ ```text
145
+ .\run.cmd arena.py play submission/bot.cpp halftime/opponents/p01.exe --seed 3
146
+ .\run.cmd arena.py batch submission/bot.cpp halftime/opponents/p01.exe --games 20
147
+ ```
148
+
149
+ 中场包解压的那一刻下半场开始计时。
150
+
151
+ ## 文件
152
+
153
+ | 文件 | 用途 |
154
+ |---|---|
155
+ | `RULES.md` | 规则 |
156
+ | `tank.hpp` | 规则引擎(单个头文件)。你的 bot 直接 `#include` 它:局面结构、精确结算一回合的 `step`、合法命令、地图生成、思考预算查询都在里面 |
157
+ | `submission/bot.cpp` | 你的 bot。现在是一个能编译、会合法行动的模板 |
158
+ | `baseline.exe` | 基准:一个中等强度的 bot,只有编译好的程序,只能用正式模式和它对战,例如 `arena.py batch submission/bot.cpp baseline.exe` |
159
+ | `tank_main.cpp` | 主循环:读局面、调你的函数、写命令、计时。不用改,正式比赛用的是同一份 |
160
+ | `arena.py`、`viewer.html` | 本地对战与复盘、网页版回放查看器 |
161
+ | `engine.py`、`core.py`、`judge/` | 裁判,与正式比赛相同 |
162
+ | `clock.py`、`run.cmd`、`run.sh` | 时钟;找到 Python 并运行脚本的启动器 |
163
+ | `build/`、`replays/`、`history/` | 运行后自动生成:编译缓存、回放、你跑过的每个版本的存档 |
164
+
165
+ 正式比赛的编译方式(一般用不到,`arena.py` 会自动编译):
166
+
167
+ ```text
168
+ g++ -std=c++20 -O2 -static -I. submission/bot.cpp tank_main.cpp -o bot.exe
169
+ ```
@@ -0,0 +1,443 @@
1
+ # 坦克大战 · 规则
2
+
3
+ 规则的精确实现是 `tank.hpp`,裁判用的就是它。**本文与 `tank.hpp` 不一致时以 `tank.hpp` 为准。** 本文只讲游戏规则和判罚;工具、接口、时钟、交卷见 `README.md`。
4
+
5
+ ## 1. 概览
6
+
7
+ 1 对 1。每方有一个基地和三辆坦克(快速型、火力型、装甲型各一辆)。每回合双方同时给自己的三辆车各下一条命令,然后按第 5 节结算。**把对方基地的血量打到 0 获胜**;打满 300 回合还没分出胜负,按第 9 节比较。
8
+
9
+ 对局里没有随机,没有隐藏信息:同样的局面、同样的命令,结算结果一定相同。规则本身对两个座位对称。
10
+
11
+ 本文用到的词:
12
+
13
+ | 词 | 含义 |
14
+ |---|---|
15
+ | 座位 | 0 号方在下(y 大的一侧),1 号方在上。坦克编号 0/1/2 = 0 号方的快速/火力/装甲,3/4/5 = 1 号方的快速/火力/装甲 |
16
+ | 格 | 地图上的一个位置 (x, y)。x 向右、y 向下,左上角是 (0,0)。"上下左右"= `U`(y−1) `D`(y+1) `L`(x−1) `R`(x+1) |
17
+ | 回合 | 一次 `step`。`s.turn` 是已经结算完的回合数,开局为 0,第一次结算的是第 1 回合。"第 T 回合末"指第 T 回合结算的最后一段(第 5 节 C) |
18
+ | 小步 | 一个回合分成两个小步。子弹每小步飞 1 格,所以每回合飞 2 格 |
19
+ | 在场 | 坦克在地图上(`alive`)。不在场的坦克不占格、不能行动 |
20
+ | 命中 | 子弹打到坦克或基地:子弹消失,坦克扣 1 血(基地见第 6 节) |
21
+ | 被毁 | 回合末,血量 ≤ 0 的坦克从场上移除 |
22
+ | 抵消 | 两发子弹相遇,都消失 |
23
+
24
+ ## 速览:时序和最容易错的几件事
25
+
26
+ 每条一句话,括号里是讲细节和例子的小节。
27
+
28
+ **一回合的顺序**(第 5 节)
29
+
30
+ 1. **开炮**:回合一开始,子弹出现在开炮的坦克自己那一格;装填中或子弹已达上限时,开炮命令当作不动。
31
+ 2. **小步 1、小步 2**,每个小步里:先坦克移动,再所有子弹前进 1 格,最后判定(命中、打墙、抵消)。
32
+ 3. **回合末**:被毁 → 复活 → 拾取 → 补给 → 判胜负。
33
+
34
+ **最容易错的**
35
+
36
+ - 一个小步结束时坦克和子弹在同一格就是命中,不管谁先到;血量被打到 0 的车到回合末才移除,这一回合照常行动、照常挡子弹(第 5 节、11.5、11.7)。
37
+ - 基地每回合最多扣 1 血,敌方的和自己人的子弹合在一起数;所以打掉一个基地至少要 24 个回合(第 6 节)。
38
+ - 子弹不分敌我:会伤队友(队友因此被毁,记对方一次击毁),打中自己的基地照样扣血(第 5 节、第 6 节、11.11)。
39
+ - 复活时压在出生点上的敌车直接被毁,不管它有多少血(第 7 节)。
40
+ - 星星在第 10、30、50……回合末出现。出星的那个回合已经站在补给点上的车,要到下一回合末才吃到(第 8 节、11.12)。
41
+ - 装填与子弹上限:0 级的快速型、装甲型开炮后下一回合不能开炮,火力型不用装填;三种车的子弹上限都是 1,上一发还在场上就打不出下一发,上限按回合开始时数。升到 1 级后快速型、装甲型不再装填,火力型的上限变成 2(第 3 节、11.10)。
42
+ - `legal_commands` 列出的命令仍可能因为别的坦克挡路、抢格而走不成(第 4 节、11.6)。`s.grid` 里只有地形:自己沿炮线逐格看地形时,路上的坦克要另外用 `tank_at` 查,子弹会先命中它(第 5 节)。
43
+
44
+ ## 2. 地图
45
+
46
+ ### 2.1 地形
47
+
48
+ 地图 15×15,x、y 都是 0–14。`s.grid[cell_of(x, y)]` 是这一格当前的地形。
49
+
50
+ | 地形 | `Cell` | 字符 | 坦克 | 子弹到这一格时 |
51
+ |---|---|---|---|---|
52
+ | 空地 | `EMPTY` | `.` | 能进 | 格里有坦克就命中,否则继续飞 |
53
+ | 砖墙 | `BRICK` | `#` | 不能进 | 子弹消失,砖墙变成空地(任何子弹都行) |
54
+ | 钢墙 | `STEEL` | `@` | 不能进 | 子弹消失;2 级的坦克打出的子弹同时把钢墙变成空地 |
55
+ | 水 | `WATER` | `~` | 不能进 | 不挡,继续飞 |
56
+ | 基地 | `BASE0` `BASE1` | `0` `1` | 不能进(哪一方的都不能) | 子弹消失,基地扣血(第 6 节) |
57
+ | 界外 | `OUTSIDE` | | 不能进 | 子弹消失 |
58
+
59
+ 被打掉的墙不会恢复。出生点和补给点只是空地上的记号,不挡坦克也不挡子弹。
60
+
61
+ ### 2.2 固定的布局
62
+
63
+ | | 0 号方(下) | 1 号方(上) | 在局面里 |
64
+ |---|---|---|---|
65
+ | 基地 | (7,14) | (7,0) | `s.base_pos[seat]` |
66
+ | 围墙(5 格砖墙,就是普通砖墙) | (6,14) (8,14) (6,13) (7,13) (8,13) | (6,0) (8,0) (6,1) (7,1) (8,1) | |
67
+ | 三个出生点 | (5,12) (7,12) (9,12) | (5,2) (7,2) (9,2) | `s.spawn[坦克编号]` |
68
+ | 三个补给点(双方共用,在中线 y=7 上) | (2,7) (7,7) (12,7) | | `s.supply[0..2]` |
69
+
70
+ 哪种车型用哪个出生点由种子决定。开局时六辆车都在自己的出生点上,0 级,没有子弹,没有星星。
71
+
72
+ 一张生成出来的图(种子 1,多砖)。这张图上 1 号方的快速、火力、装甲分别出生在 (7,2) (9,2) (5,2),0 号方的在 (7,12) (5,12) (9,12):
73
+
74
+ ```
75
+ x=012345678901234 (x 的个位数)
76
+ y= 0 ...####1#.#.##.
77
+ y= 1 .#....###.##...
78
+ y= 2 .#..........@@#
79
+ y= 3 #..........#..#
80
+ y= 4 #....#....###.#
81
+ y= 5 .~~#.#...###..#
82
+ y= 6 ...#.##.@....##
83
+ y= 7 ##.#........... 中线
84
+ y= 8 ...#.##.@....##
85
+ y= 9 .~~#.#...###..#
86
+ y=10 #....#....###.#
87
+ y=11 #..........#..#
88
+ y=12 .#..........@@#
89
+ y=13 .#....###.##...
90
+ y=14 ...####0#.#.##.
91
+ ```
92
+
93
+ ### 2.3 生成器保证的事
94
+
95
+ 地图由 `generate_map(种子, 风格, 参数)` 生成,`check_layout` 检查下面的保证。
96
+
97
+ 1. **地形上下镜像**:(x, y) 和 (x, 14−y) 的地形相同(两个基地互为镜像)。左右不保证对称。
98
+ 2. **车型排布左右对调**:1 号方某种车型出生在 (x, 2),0 号方的同一车型就出生在 (14−x, 12)。上图里 1 号方的火力型在 (9,2),0 号方的火力型在 (5,12);和 (9,2) 同一列相对的 (9,12) 上是 0 号方的装甲型。只有正前(x=7)那一列相对的两辆是同一种车型。地形左右不对称,所以同一种车型在两个座位上面对的地形并不相同。
99
+ 3. **连通**:把砖墙当成空地(钢墙和水不能过)时,两个基地、六个出生点、三个补给点互相连通。只保证到这一步:实际走过去可能要先打掉砖墙。
100
+ 4. **每个基地至少能从左、右、正前三个方向中的两个被打到**。一个方向"能被打到"是指:从基地沿这个方向往外的那条线上,围墙之外有一格能站坦克(空地或砖墙),它和基地之间没有钢墙(砖墙、水不算挡),并且进攻方的坦克不穿过这个基地的围墙就能走到这一格(砖墙当空地,钢墙和水不能过)。`base_open_sides(layout, seat)` 给出具体是几个。
101
+ 5. 最靠边的两行(y=0、1 和 y=13、14)里,x=3–11 的范围内没有钢墙和水:除了基地,只可能是空地或砖墙。
102
+ 6. 每个出生点和它上下左右四格开局时都是空地;唯一的例外是正前出生点靠基地的那一格,它是围墙。补给点都是空地。
103
+
104
+ ### 2.4 四种风格
105
+
106
+ | 风格 | `Style` | 平均每张图的砖墙(含 10 格围墙) | 钢墙 | 水 |
107
+ |---|---|---:|---:|---:|
108
+ | 开阔 | `STYLE_OPEN` (0) | 29 格 | 4 格 | 4 格 |
109
+ | 多砖 | `STYLE_BRICK` (1) | 74 格 | 6 格 | 4 格 |
110
+ | 多水 | `STYLE_WATER` (2) | 29 格 | 4 格 | 32 格 |
111
+ | 多钢 | `STYLE_STEEL` (3) | 34 格 | 26 格 | 4 格 |
112
+
113
+ 风格传 −1(`make_game(种子)` 的默认)时按种子轮换:种子除以 4 的余数就是风格编号。
114
+
115
+ **正式赛的地图来自同一个生成器、同样的参数,只是种子不同。**
116
+
117
+ ## 3. 坦克
118
+
119
+ | 车型 | 编号(0 号方 / 1 号方) | 初始血量 | 每回合移动 | 子弹上限 | 装填 |
120
+ |---|---|---:|---|---:|---|
121
+ | 快速型 `FAST` | 0 / 3 | 1 | 最多 2 步 | 1 | 开炮后装填 1 回合 |
122
+ | 火力型 `FIRE` | 1 / 4 | 1 | 1 步 | 1 | 不用装填 |
123
+ | 装甲型 `ARMOR` | 2 / 5 | 2 | 1 步 | 1 | 开炮后装填 1 回合 |
124
+
125
+ - **子弹上限**:这辆车打出去、还留在场上的子弹数(`bullets_out`)不能超过它(`bullet_cap`)。
126
+ - **装填 1 回合**:第 T 回合开炮成功,第 T+1 回合不能开炮,第 T+2 回合起可以(`reload_left` 是还要等几个回合)。
127
+ - `can_fire(s, i)`:这辆车下一回合下开炮命令是否真的会打出子弹。
128
+ - 坦克没有朝向,任何时候可以朝任何方向移动或开炮。
129
+
130
+ ### 升级
131
+
132
+ 吃一颗星升一级(第 8 节),最高 2 级。
133
+
134
+ | | 升到 1 级 | 升到 2 级 |
135
+ |---|---|---|
136
+ | 快速型、装甲型 | 当前血量 +2;不再需要装填(子弹上限仍是 1) | 当前血量 +2;此后打出的子弹能打掉钢墙 |
137
+ | 火力型 | 当前血量 +2;子弹上限变成 2 | 同上 |
138
+
139
+ - 1 级的效果到 2 级仍然保留。一辆车最多靠升级 +4 血。加的是当前血量,受过伤的车升级后是"当前血量 +2"。
140
+ - 能否打掉钢墙按**开炮时**的等级定(子弹的 `steel` 标记),之后升级不影响已经打出的子弹。
141
+ - 装填和子弹上限按**当前**等级算。
142
+ - 被毁后等级清零,升级得到的东西全部失去。
143
+
144
+ ## 4. 命令
145
+
146
+ 每回合给每辆车一条命令(`Orders` 的 `cmd[FAST]`、`cmd[FIRE]`、`cmd[ARMOR]`)。
147
+
148
+ | 命令 | 文本 | 编码 | 构造 |
149
+ |---|---|---|---|
150
+ | 不动 | `S` | 0 | `STAY` |
151
+ | 走一步 | `U` `D` `L` `R` | 1–4 | `cmd_move(d)` |
152
+ | 开炮 | `FU` `FD` `FL` `FR` | 5–8 | `cmd_fire(d)` |
153
+ | 连走两步(只有快速型) | 两个方向字母,如 `UL` = 先上后左 | 9–24 | `cmd_move2(d1, d2)` |
154
+
155
+ 方向 `d`:`UP`=0、`DOWN`=1、`LEFT`=2、`RIGHT`=3。快速型可以只走一步;两步的方向可以不同。开炮的回合不移动。
156
+
157
+ **当作不动、不算犯规的情况:**
158
+
159
+ - 移动的目标格不是空地,或者被别的坦克挡住、抢格(第 5 节第 2 步):这一步没走成。
160
+ - 开炮,但正在装填或子弹已达上限:不开炮,也不重新计装填。
161
+ - 给不在场的坦克下的任何命令。
162
+ - 编码超出 0–24。
163
+
164
+ **不合法的情况:给火力型或装甲型下两步。** 这一回合你的三辆车都不动,并记一次失败(第 10 节)。注意 `step` 自己不做这个处罚:直接调 `step` 时,非快速型的两步只执行第一步。自己前推时别依赖这一点。
165
+
166
+ `legal_commands(s, i, out)` 列出一辆车此刻"有意义"的命令(去掉了必然等于不动、或必然等于另一条的写法)。列出的命令仍可能因为别的坦克挡路、抢格而没走成。
167
+
168
+ ## 5. 回合结算
169
+
170
+ 一回合 = **A 开炮 → B 小步 1 → B 小步 2 → C 回合末**。全文最重要的一节:任何局面的结果都由下面的步骤决定。
171
+
172
+ ### A. 开炮(回合开始时)
173
+
174
+ 1. 每辆在场、下了开炮命令的坦克,同时满足下面两条就开炮成功:
175
+ - 它留在场上的子弹数小于子弹上限。按**此刻**数:本回合稍后才消失的子弹,下一回合才空出名额。
176
+ - 它不在装填中。
177
+
178
+ 开炮成功:在**这辆坦克自己所在的格**生成一发子弹,带方向、所属坦克、能否打掉钢墙。子弹在本回合的两个小步里照常前进。
179
+ 不成功:这条命令当作不动。
180
+
181
+ ### B. 小步(做两遍:小步 1、小步 2)
182
+
183
+ 2. **坦克移动。** 小步 1:所有下了移动命令的坦克走第一步。小步 2:只有下了两步的快速型走第二步。要走的坦克同时走。一步走成要同时满足三条,否则这一步没走成、留在原地:
184
+ - 目标格此刻是空地;
185
+ - 目标格在本小步**开始时**没有坦克;
186
+ - 没有别的坦克在本小步也要进同一格。
187
+
188
+ 快速型第一步没走成,第二步照常从它当时所在的格子独立判定。
189
+ 3. **所有子弹前进 1 格**(包括本回合刚打出的)。
190
+ 4. **判定**,按下面的顺序。已经消失的子弹不再参加后面的判定。
191
+ 1. **子弹对穿**:两发子弹在本小步互换了位置,都消失。
192
+ 2. **子弹与坦克互换位置**:一发子弹和一辆坦克在本小步互换了位置,算命中这辆坦克。
193
+ 3. **其余每发子弹看它所在的格**:
194
+ - 界外:子弹消失。
195
+ - 砖墙:子弹和砖墙都消失。
196
+ - 钢墙:子弹消失;能打掉钢墙的子弹让钢墙也消失。
197
+ - 水:什么都不发生。
198
+ - 基地:子弹消失,基地扣血(第 6 节)。
199
+ - 空地且格里有坦克:命中。同一格里有几发就命中几次。不分敌我。
200
+
201
+ 墙在这一条全部子弹处理完之后才消失,所以同一小步打中同一堵墙的几发子弹都消失。
202
+ 4. **同格抵消**:剩下的子弹里,同一格有两发或更多的,全部消失。不要求方向相对,不分敌我,在水面上也一样。
203
+
204
+ 血量被打到 0 或更低的坦克此时**不移除**,要等到回合末。
205
+
206
+ ### C. 回合末(按顺序)
207
+
208
+ 5. **被毁**:血量 ≤ 0 的坦克从场上移除,等级清零;对方的击毁数 `kills` +1,不论是被谁打的。
209
+ 6. **复活**(第 7 节)。
210
+ 7. **拾取**(第 8 节)。
211
+ 8. **补给**(第 8 节)。
212
+ 9. **判胜负**(第 9 节)。
213
+
214
+ ### 直接推论
215
+
216
+ - 移动在子弹前进之前,判定在两者之后。所以一个小步里,坦克和子弹最后停在同一格就是命中,不管谁先到。
217
+ - 开炮的坦克不会被自己刚打出的子弹命中。
218
+ - 子弹不分敌我:会命中队友,会打自己的基地,同一方的子弹也互相抵消。
219
+ - 本回合被毁的坦克,本回合的命令照常完整执行(炮照开、步照走)。
220
+
221
+ ## 6. 基地
222
+
223
+ - 血量 24(`s.base_hp[seat]`),不会恢复。
224
+ - **每个基地每回合最多扣 1 血。** 一回合里命中同一个基地的子弹,两个小步合计、敌方的和自己人的合在一起数,只有一发扣血;其余的照样消失,但不扣血。两个基地各自计数。所以一个基地最快 24 个回合被打掉。
225
+ - **打自己的基地照样扣血**,也照样占掉这一回合的那 1 血。
226
+ - 基地外的围墙是普通砖墙,要先打掉(或绕到没有砖的方向)才打得到基地。
227
+ - `s.base_dmg[seat]` 记这一方对敌方基地造成的伤害,只是统计,不参与胜负。自己人打自己的基地不记;同一回合敌我都命中而只扣 1 血时,先到的小步优先,同一小步里记给敌方。
228
+
229
+ ## 7. 复活
230
+
231
+ | | |
232
+ |---|---|
233
+ | 时间 | 第 T 回合末被毁 → 第 T+1、T+2、T+3 回合不在场(给它的命令被忽略)→ **第 T+3 回合末复活** → 第 T+4 回合起行动。`respawn_in(s, i)` 是还要等几个回合末 |
234
+ | 位置和状态 | 自己的出生点 `s.spawn[i]`;车型的初始血量,0 级,不带装填 |
235
+ | 出生点被**己方**坦克占着 | 不复活。之后每个回合末再看一次,出生点空出来的那个回合末复活 |
236
+ | 出生点被**敌方**坦克占着 | 那辆敌车被毁(不管它有多少血;记复活方一次击毁;它自己 3 回合后复活),本车照常复活 |
237
+ | 出生点上的子弹 | 直接消失,不算命中 |
238
+
239
+ - "占着"按回合末第 5 步之后的位置看:本回合刚被毁的车让出的出生点,当回合就能用;在复活的那个回合才走上敌方出生点的车同样被毁,在那个回合走开的没事。
240
+ - 被毁的车已经打出的子弹继续飞,仍然算它的(11.9)。
241
+
242
+ ## 8. 补给与星星
243
+
244
+ - **出星**:第 10 回合末出第一批,之后每 20 回合一批(第 30、50、…、290 回合末),一局共 15 批。每一批里,每个**当前没有星星**的补给点出一颗;已经有星星的点不再出。点上站着坦克也照常出。`star_at(s, cell)` 查某一格有没有星星。
245
+ - **拾取**(回合末,在补给之前):在场、未满级的坦克站在有星星的补给点上,就吃掉它:升一级,当前血量 +2,己方吃星数 `stars_eaten` +1。哪一方的车都能吃任何一个补给点的星。
246
+ - 拾取在补给之前,所以:
247
+ - 出星的那个回合已经站在补给点上的车,要到**下一回合末**才吃到(那时还站在上面的话);
248
+ - 一辆车一回合最多升一级。
249
+ - 本回合被毁的坦克不拾取。
250
+ - **满级(2 级)的坦克不拾取**:星星留在原地,不计入吃星数;别的车来了照样能吃。
251
+
252
+ ## 9. 胜负
253
+
254
+ 回合末第 9 步:
255
+
256
+ 1. 一方基地血量为 0、另一方大于 0:另一方获胜。
257
+ 2. 两个基地**同一回合**归零,或者第 300 回合结算完两个基地都还在:依次比较下面三项,先分出大小的那一项决定胜负,数值大的一方胜;三项都相同是平局。
258
+
259
+ | 顺序 | 项目 | 字段 |
260
+ |---|---|---|
261
+ | 1 | 基地血量 | `s.base_hp` |
262
+ | 2 | 击毁数 | `s.kills` |
263
+ | 3 | 吃星数 | `s.stars_eaten` |
264
+
265
+ - 基地在小步 1 就归零时,这一回合照常结算到底,所以可能两个基地同一回合归零。这时血量都是 0,实际从击毁数比起。
266
+ - 击毁数:对方的坦克每被毁一次记一次,不论被谁打的。被队友打掉、压在对方出生点上被毁都算。
267
+ - 计分:胜 1、平 0.5、负 0。
268
+ - 结束后 `s.done` 为真,`s.winner` 是胜方座位(平局为 −1),之后再 `step` 局面不变。`reason_text(s)` 是结束原因:`base`(基地被打掉)、`hp` / `kills` / `stars`(比较时由这一项决出)、`draw`;两个基地同回合归零时前面加 `both_`(如 `both_kills`),也可以用 `both_bases_down(s)` 判断。
269
+
270
+ ## 10. 判罚
271
+
272
+ | 项目 | 规定 |
273
+ |---|---|
274
+ | 预算的单位 | CPU 毫秒:你的程序自己消耗的 CPU 时间。被挂起、等输入的时间不算 |
275
+ | 首回合 | 2000 CPU 毫秒,从程序启动算起,含 `init` 和第一次 `decide` |
276
+ | 之后每回合 | 50 CPU 毫秒,从交出上一回合的命令算到交出这一回合的命令 |
277
+ | 多线程 | 按整个进程所有线程合计。`budget_left()` 只看得到调用它的那个线程 |
278
+ | 墙上时钟的保险上限 | 首回合 4 秒、之后每回合 2 秒没有回复,记一次失败 |
279
+ | 超出预算 | 这一回合你的三辆车都不动,记一次失败。裁判不等你:超出之后继续算掉的时间记在下一回合头上,所以一回合用掉 150 CPU 毫秒以上就是连续 3 次失败 |
280
+ | 不合法的命令 | 给火力型或装甲型下两步:这一回合三辆车都不动,记一次失败 |
281
+ | 连续失败 | 超预算和不合法的命令合在一起数,**连续 3 次判负**。中间有一个回合正常,就从头数 |
282
+ | 崩溃、退出 | 直接判负 |
283
+ | 判负的那一回合 | 不结算,对局就此结束,结果里的原因是 `fault`。双方在同一回合判负算平局 |
284
+
285
+ ## 11. 容易想错的情况
286
+
287
+ 下面的坐标都是 (x, y),"右"是 x+1。命令用第 4 节的文本写法。每一条都用 `tank.hpp` 实际结算核对过。
288
+
289
+ ### 11.1 贴脸开炮
290
+
291
+ A 在 (5,5) 下 `FR`,B 在右边紧挨着的 (6,5)。子弹从 (5,5) 出发,小步 1 到 (6,5),小步 2 到 (7,5)。
292
+
293
+ | B 的命令 | 小步 1 之后 | 小步 2 之后 | 结果 |
294
+ |---|---|---|---|
295
+ | 不动 `S` | 子弹到 (6,5),B 在那里 | | 小步 1 命中 |
296
+ | 朝 A 走 `L` | 被 A 挡住没走成,同上 | | 小步 1 命中 |
297
+ | 沿炮线后退 1 格 `R` | B 在 (7,5),子弹在 (6,5) | 子弹到 (7,5),B 在那里 | 小步 2 命中 |
298
+ | 横向让开 `U` | B 在 (6,4),子弹在 (6,5) | 子弹在 (7,5) | 没中,子弹继续飞 |
299
+ | 快速型连退 2 格 `RR` | B 在 (7,5),子弹在 (6,5) | B 在 (8,5),子弹在 (7,5) | 本回合没中 |
300
+ | 快速型先退后让 `RU` | B 在 (7,5),子弹在 (6,5) | B 在 (7,4),子弹在 (7,5) | 没中 |
301
+
302
+ 快速型连退 2 格之后,子弹就在它身后 1 格、仍朝它飞,这又是一次"贴脸":下一回合它不动或只退 1 格仍被命中,再连退 2 格或横向让开才不中。
303
+
304
+ 隔一格开炮(B 在 (7,5)):B 不动,小步 2 命中;B 后退 1 格,本回合不中,子弹追到它身后 1 格。
305
+
306
+ ### 11.2 面对面互射
307
+
308
+ A 在 (5,5) 下 `FR`,B 在同一行右边下 `FL`。不管相距几格,两发子弹都抵消,两车都没事:
309
+
310
+ | B 的位置 | 发生了什么 |
311
+ |---|---|
312
+ | (6,5),紧挨着 | 小步 1 两发子弹互换位置(对穿),都消失 |
313
+ | (7,5) | 小步 1 两发子弹同时到 (6,5)(同格),都消失 |
314
+ | (8,5) | 小步 2 对穿 |
315
+ | (9,5) | 小步 2 同格 |
316
+
317
+ ### 11.3 朝着飞来的子弹开炮,能把它抵消
318
+
319
+ T 在 (5,5),一发子弹在同一行右边朝左飞来。T 下 `FR`(朝来弹的方向开炮):T 的子弹先和来弹对穿或同格,来弹到不了 T。来弹在 (6,5)、(7,5)、(8,5)、(9,5) 时都是如此。
320
+
321
+ 前提是这一炮真的打得出去。下面两种车的 `FR` 当作不动,照样被命中(来弹在 (6,5) 时小步 1 命中,在 (7,5) 时小步 2 命中):
322
+
323
+ - 装填中的车(0 级的快速型、装甲型上一回合刚开过炮);
324
+ - 子弹已达上限的车(比如它朝别处打的上一发还在飞)。
325
+
326
+ ### 11.4 子弹相遇:对穿、同格、错过
327
+
328
+ - 两发垂直飞行的子弹在同一小步到达同一格:抵消。例:子弹甲在 (4,5) 朝右,子弹乙在 (6,3) 朝下,小步 2 同时到 (6,5),都消失。
329
+ - 先后经过同一格不算相遇。例:乙改在 (6,4) 朝下,小步 1 乙过 (6,5),小步 2 甲才到 (6,5),两发都继续飞。
330
+ - 同一方的两发子弹也抵消;在水面上相遇也抵消;三发同时到同一格,三发都消失。
331
+ - **格里有坦克时不抵消,都算命中。** 例:A 在 (5,5) 下 `FR`,C 在 (7,5) 下 `FL`,中间 (6,5) 有一辆 2 血的坦克:小步 1 两发都命中,它扣 2 血。
332
+
333
+ ### 11.5 子弹与移动中的坦克
334
+
335
+ 一发子弹在 (6,5),坦克 T 走一步:
336
+
337
+ | 子弹的方向 | T 的位置和命令 | 结果 |
338
+ |---|---|---|
339
+ | 朝左 | T 在 (5,5) 下 `R`,迎着子弹走 | T 到 (6,5),子弹到 (5,5):互换位置,**算命中** |
340
+ | 朝左 | T 在 (6,6) 下 `U`,从侧面走进子弹此刻所在的格 | 没中:子弹同一小步飞到 (5,5) 去了 |
341
+ | 朝右 | T 在 (5,5) 下 `R`,跟在子弹后面 | 没中 |
342
+
343
+ 要看的是这一小步走完之后子弹到哪一格,不是它现在在哪一格:子弹在 (7,5) 朝左飞,T 在 (6,6) 下 `U` 走进 (6,5),小步 1 子弹也到 (6,5),命中。
344
+
345
+ ### 11.6 两车抢格、互换、跟随
346
+
347
+ 不分敌我,没有谁优先。
348
+
349
+ | 局面 | 命令 | 结果 |
350
+ |---|---|---|
351
+ | A 在 (4,5),B 在 (6,5) | A `R`,B `L`,都要进 (5,5) | 都没走成 |
352
+ | A 在 (4,5),B 在 (5,5),面对面 | A `R`,B `L`,想互换 | 都没走成 |
353
+ | A 在 (5,5),B 在它身后 (4,5) | A `R`,B `R` | A 到 (6,5);B 没走成((5,5) 在小步开始时有车) |
354
+ | 同上,B 是快速型 | A `R`,B `RR` | B 第一步没走成,第二步走进 A 让出的 (5,5) |
355
+ | 快速型 A 在 (5,5),B 在它身后 (4,5) | A `RR`,B `R` | A 到 (7,5);B 只有小步 1 的机会,没走成 |
356
+ | 快速型 A 在 (4,5),快速型 B 在 (8,5) | A `RR`,B `LL` | 小步 2 都要进 (6,5),都没走成:A 停在 (5,5),B 停在 (7,5) |
357
+
358
+ 没走成的车仍占着原来的格子,想进它那一格的别的车同样没走成。
359
+
360
+ ### 11.7 0 血的坦克到回合末才移除
361
+
362
+ 被打到 0 血的坦克在本回合剩下的时间里仍然在场:仍占格,仍挡子弹,快速型仍走它的第二步。
363
+
364
+ - **仍挡子弹。** X(1 血)在 (6,5),Y 在它正下方 (6,6)。A 在 (5,5) 下 `FR`,另有一发子弹在 (6,3) 朝下飞。小步 1:A 的子弹命中 X,X 剩 0 血。小步 2:朝下的那发到 (6,5),仍然命中 X,然后消失。回合末 X 被毁(只记一次击毁),Y 没事。如果没有 X,这发子弹下一回合小步 1 就命中 Y。
365
+ - **仍占格。** 同样是 A 打 X,另一辆快速型在 (7,4) 下 `DL`:第二步要进 (6,5),X 还在那里,没走成,停在 (7,5)。下一回合那一格才空出来。
366
+ - **仍走第二步。** 快速型在 (6,5) 下 `RR`,小步 1 走到 (7,5) 时被一发子弹命中到 0 血:小步 2 它仍走到 (8,5),回合末在 (8,5) 被毁。
367
+ - **炮照样打出去。** 开炮在回合开始时,先于一切命中。A 打 B、C 打 A 发生在同一回合:A 和 B 都被毁,B 这回合朝别处开的炮照样留在场上。
368
+
369
+ ### 11.8 快速型第二步能走进小步 1 刚被打掉的墙
370
+
371
+ 快速型 F 在 (6,5),右边 (7,5) 是砖墙。队友在 (7,4) 下 `FD`,小步 1 把这块砖打掉。
372
+
373
+ - F 下 `RR`:第一步没走成(移动在子弹之前,墙还在),第二步走进 (7,5)。
374
+ - F 下 `R`:没走成,留在 (6,5)。
375
+ - 如果砖墙是小步 2 才被打掉的(队友在 (7,3) 开炮),F 下 `RR` 两步都没走成。
376
+
377
+ 钢墙被 2 级的子弹在小步 1 打掉时也一样。所以一步能不能走成,下命令时不总能确定;`legal_commands` 因此保留了"第二步朝砖墙或钢墙走"的两步命令。
378
+
379
+ ### 11.9 被毁前打出的子弹,复活后仍占子弹名额
380
+
381
+ 火力型(子弹上限 1)第 1 回合在 (0,5) 下 `FR`,同一回合被毁。它第 4 回合末复活,可那发子弹还在往右飞,第 8 回合小步 1 才出界。所以第 5、6、7、8 回合它的开炮命令都当作不动(第 8 回合也不行:上限按回合开始时数),第 9 回合才能开炮。
382
+
383
+ ### 11.10 装填的边界
384
+
385
+ - 0 级的快速型、装甲型:第 T 回合开炮,第 T+1 回合不能,第 T+2 回合能。火力型每回合都能(只要子弹没到上限)。
386
+ - **没打出去的炮不重新计装填。** 装甲型第 1 回合开炮,子弹要到第 3 回合才消失。第 2 回合下开炮:装填中,当作不动。第 3 回合下开炮:装填已经完了,但上一发还在场上,当作不动。第 4 回合能开。
387
+ - **子弹上限按回合开始时数。** 上一发在本回合小步 1 就会消失,这一回合也打不出下一发。
388
+ - **1 级的快速型、装甲型不再装填,但子弹上限仍是 1。** 贴着目标打(子弹当回合消失)能每回合一发;朝远处打,上一发还在飞就打不出下一发。1 级的火力型上限是 2,可以连开两回合。
389
+ - **装填按当前等级算。** 0 级的快速型第 T 回合开炮,当回合末吃星升到 1 级,第 T+1 回合就能再开。
390
+ - 复活后不带装填。
391
+
392
+ ### 11.11 友军伤害和击毁数
393
+
394
+ - 打中队友,队友照样扣血。队友因此被毁,记**对方**一次击毁。
395
+ - 压在对方出生点上被对方的复活毁掉,记对方一次击毁。
396
+ - 一辆车同一回合挨了比血量更多的子弹,也只被毁一次、只记一次击毁。
397
+ - 打自己的基地扣自己基地的血,不记到任何一方的 `base_dmg` 里。
398
+
399
+ ### 11.12 出星的回合站在补给点上
400
+
401
+ 一辆车在第 10 回合之前就停在补给点 (7,7) 上。第 10 回合末:先拾取(这时还没有星星),再补给,星星出现在它脚下。它要到第 11 回合末才吃到;第 11 回合走开了就吃不到,星星留在原地。
402
+
403
+ 另一辆车第 11 回合走上有星星的 (12,7):第 11 回合末吃到。
404
+
405
+ ### 11.13 满级的车停在补给点上
406
+
407
+ 2 级的车站在有星星的补给点上:不吃,星星一直留着,吃星数不变。下一批出星时这个点已经有星星,不再出新的,另外两个点照常出。它走开后别的车(哪一方的都行)走上去就能吃。
408
+
409
+ ## 12. 规则数值速查表
410
+
411
+ 名字是 `tank.hpp` 里 `Params` 的成员(局面里是 `s.P`)。**文档与头文件不一致时以头文件为准。**
412
+
413
+ | 规则 | 名字 | 默认值 |
414
+ |---|---|---|
415
+ | 地图边长 | `size` | 15 |
416
+ | 基地外的砖墙层数 | `ring` | 1 |
417
+ | 三辆车在围墙外一格出生 | `spawn_near` | true |
418
+ | 两边的车型排布左右对调 | `spawn_flip` | true |
419
+ | 补给点数量 | `supply_points` | 3 |
420
+ | 基地血量 | `base_hp` | 24 |
421
+ | 基地每回合最多扣几血 | `base_hit_cap` | 1 |
422
+ | 快速型:血量、每回合步数、子弹上限、装填回合 | `types[FAST]` 的 `hp` `speed` `bullets` `reload` | 1、2、1、1 |
423
+ | 火力型:同上 | `types[FIRE]` | 1、1、1、0 |
424
+ | 装甲型:同上 | `types[ARMOR]` | 2、1、1、1 |
425
+ | 最高等级 | `max_level` | 2 |
426
+ | 每升一级加的血 | `level_hp` | 2 |
427
+ | 1 级起要装填的车型不再装填 | `level1_no_reload` | true |
428
+ | 1 级起不装填的车型(火力型)子弹上限加几 | `level1_bullets` | 1 |
429
+ | 2 级起打出的子弹能打掉钢墙 | `level2_steel` | true |
430
+ | 第一批星星在第几回合末 | `supply_first` | 10 |
431
+ | 之后每几回合一批 | `supply_period` | 20 |
432
+ | 被毁后第几个回合末复活 | `respawn` | 3 |
433
+ | 复活时出生点上的敌方坦克被毁 | `respawn_telefrag` | true |
434
+ | 复活时出生点上的子弹消失 | `respawn_clear_bullets` | true |
435
+ | 子弹对穿、同格时抵消 | `bullet_cancel` | true |
436
+ | 回合上限 | `max_turns` | 300 |
437
+ | 比较的项目和顺序 | `tiebreak[0..n_tiebreak)` | 基地血量、击毁数、吃星数(`n_tiebreak` = 3) |
438
+ | 首回合预算(CPU 毫秒) | 常量 `BUDGET_FIRST_MS` | 2000 |
439
+ | 之后每回合预算(CPU 毫秒) | 常量 `BUDGET_TURN_MS` | 50 |
440
+ | 墙上保险上限:首回合、之后每回合(毫秒) | 常量 `BUDGET_WALL_FIRST_MS`、`BUDGET_WALL_TURN_MS` | 4000、2000 |
441
+ | 连续几次失败判负 | 裁判里的常量 | 3 |
442
+
443
+ `Params` 里其余的成员(`start_level`、`base_armor`、`base_regen`,以及名字以 `proto_` 开头的三个)在本届比赛中固定为默认值,不起作用。