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,1205 @@
1
+ // 坦克大战 bot
2
+ //
3
+ // 打法:火力车压主攻线,装甲车扛线和补刀,快速车抢星兼侧袭。
4
+ // 每回合枚举己方全部合法命令,对「暗杀 / 冲基地 / 原地吃子弹」三种对手模型做一回合精确推演,
5
+ // 再把最好的一批和几套脚本策略放进多回合推演(双方都用同一套走子策略)里比较,取最坏情况下最好的一着。
6
+ // 墙图用「到最佳炮位的代价」做梯度;星星按刷新窗口加权。时间用 budget_left 卡住,超预算前停手。
7
+ #include "tank.hpp"
8
+
9
+ #include <cstdint>
10
+ #include <cstdio>
11
+ #include <cstring>
12
+
13
+ using namespace tank;
14
+
15
+ using Score = long long;
16
+
17
+ static constexpr int INF_D = 9999;
18
+ static constexpr int QCAP = 4096;
19
+ static constexpr Score WIN_SC = 1000000000000LL;
20
+
21
+ #ifndef FIXED_NODES
22
+ #define FIXED_NODES 0
23
+ #endif
24
+
25
+ static int g_me = 0, g_en = 1;
26
+ static int g_stuck[3] = {};
27
+ static int g_lastpos[3] = {-1, -1, -1};
28
+ static Cmd g_lastcmd[3] = {};
29
+ static long g_nodes = 0;
30
+ static bool g_stop = false;
31
+
32
+ enum Mode : int { M_BAL = 0, M_AGG = 1, M_STAR = 2, M_DEF = 3, M_N = 4 };
33
+
34
+ // --------------------------------------------------------------------------
35
+ // 小工具
36
+ // --------------------------------------------------------------------------
37
+
38
+ static bool time_ok(double reserve) {
39
+ #if FIXED_NODES
40
+ (void)reserve;
41
+ return g_nodes < FIXED_NODES;
42
+ #else
43
+ return budget_left() >= reserve;
44
+ #endif
45
+ }
46
+
47
+ static int iabs(int x) { return x < 0 ? -x : x; }
48
+
49
+ static int manh(int a, int b) {
50
+ return iabs(cell_x(a) - cell_x(b)) + iabs(cell_y(a) - cell_y(b));
51
+ }
52
+
53
+ // 离 seat 基地最近的活敌。base_d 小表示已经摸进家里。
54
+ struct Threat {
55
+ int id;
56
+ int base_d;
57
+ int pos;
58
+ };
59
+
60
+ static Threat nearest_threat(const State& s, int seat) {
61
+ Threat th{-1, 99, 0};
62
+ const int b = s.base_pos[seat];
63
+ for (int ty = 0; ty < 3; ++ty) {
64
+ const int ei = tank_id(1 - seat, ty);
65
+ if (!s.tank[ei].alive) continue;
66
+ const int d = manh(s.tank[ei].pos, b);
67
+ if (d < th.base_d) {
68
+ th.id = ei;
69
+ th.base_d = d;
70
+ th.pos = s.tank[ei].pos;
71
+ }
72
+ }
73
+ return th;
74
+ }
75
+
76
+ static int star_wait(int turn) {
77
+ if (turn < 10) return 10 - turn;
78
+ const int m = (turn - 10) % 20;
79
+ return m == 0 ? 0 : 20 - m;
80
+ }
81
+
82
+ struct Ray {
83
+ int kind; // 0 出界/空, 1 砖, 2 钢, 3 敌方基地, 4 己方基地, 5 坦克
84
+ int id;
85
+ int dist;
86
+ int cell;
87
+ };
88
+
89
+ static Ray trace(const State& s, int pos, int dir, int seat) {
90
+ Ray r{0, -1, 0, pos};
91
+ for (int c = pos + DIR_DELTA[dir], n = 1; n <= 20; ++n, c += DIR_DELTA[dir]) {
92
+ const int ti = tank_at(s, c);
93
+ if (ti >= 0) return Ray{5, ti, n, c};
94
+ const uint8_t g = s.grid[c];
95
+ if (g == EMPTY || g == WATER) continue;
96
+ if (g == BRICK) return Ray{1, -1, n, c};
97
+ if (g == STEEL) return Ray{2, -1, n, c};
98
+ if (g == BASE0 || g == BASE1) return Ray{(g - BASE0) == seat ? 4 : 3, -1, n, c};
99
+ return Ray{0, -1, n, c};
100
+ }
101
+ return r;
102
+ }
103
+
104
+ // 只看地形:沿方向到敌方基地要打掉几块墙。打不到返回 99。
105
+ static int bricks_to_base(const State& s, int cell, int dir, int enemy_base, bool steel_ok) {
106
+ int br = 0;
107
+ for (int c = cell + DIR_DELTA[dir], n = 0; n < 20; ++n, c += DIR_DELTA[dir]) {
108
+ if (c == enemy_base) return br;
109
+ const uint8_t g = s.grid[c];
110
+ if (g == EMPTY || g == WATER) continue;
111
+ if (g == BRICK) {
112
+ ++br;
113
+ continue;
114
+ }
115
+ if (g == STEEL) {
116
+ if (!steel_ok) return 99;
117
+ ++br;
118
+ continue;
119
+ }
120
+ return 99;
121
+ }
122
+ return 99;
123
+ }
124
+
125
+ // --------------------------------------------------------------------------
126
+ // 距离场:每个空地到「好炮位」的行进代价,以及到补给点的代价。按墙哈希缓存。
127
+ // --------------------------------------------------------------------------
128
+
129
+ struct Entry {
130
+ uint64_t hash = 0;
131
+ bool valid = false;
132
+ int atk[2][CELLS];
133
+ int atkS[2][CELLS];
134
+ int sup[MAX_SUPPLY][CELLS];
135
+ };
136
+
137
+ static Entry g_cache[24];
138
+ static int g_clock = 0;
139
+
140
+ static int enter_w(uint8_t g, int brick_w, int steel_w) {
141
+ if (g == EMPTY) return 1;
142
+ if (g == BRICK) return brick_w;
143
+ if (g == STEEL) return steel_w;
144
+ return INF_D;
145
+ }
146
+
147
+ static void spfa(const State& s, int* dist, int brick_w, int steel_w) {
148
+ unsigned char inq[CELLS];
149
+ std::memset(inq, 0, sizeof inq);
150
+ int q[QCAP];
151
+ int qh = 0, qt = 0, qc = 0;
152
+ auto push = [&](int c) {
153
+ if (inq[c] || dist[c] >= INF_D) return;
154
+ inq[c] = 1;
155
+ q[qt] = c;
156
+ qt = (qt + 1) & (QCAP - 1);
157
+ ++qc;
158
+ };
159
+ for (int c = 0; c < CELLS; ++c)
160
+ if (dist[c] < INF_D) push(c);
161
+ int guard = 0;
162
+ while (qc > 0 && guard < CELLS * 16) {
163
+ ++guard;
164
+ const int c = q[qh];
165
+ qh = (qh + 1) & (QCAP - 1);
166
+ --qc;
167
+ inq[c] = 0;
168
+ const int dc = dist[c];
169
+ for (int d = 0; d < 4; ++d) {
170
+ const int n = c + DIR_DELTA[d];
171
+ const int w = enter_w(s.grid[n], brick_w, steel_w);
172
+ if (w >= INF_D) continue;
173
+ const int nd = dc + w;
174
+ if (nd < dist[n]) {
175
+ dist[n] = nd;
176
+ push(n);
177
+ }
178
+ }
179
+ }
180
+ }
181
+
182
+ static void seed_attack(const State& s, int seat, bool steel_ok, int* dist) {
183
+ const int eb = s.base_pos[1 - seat];
184
+ for (int c = 0; c < CELLS; ++c) dist[c] = INF_D;
185
+ const int sz = s.P.size;
186
+ for (int y = 0; y < sz; ++y) {
187
+ for (int x = 0; x < sz; ++x) {
188
+ const int c = cell_of(x, y);
189
+ if (s.grid[c] != EMPTY) continue;
190
+ int best = 99, blen = 99;
191
+ for (int d = 0; d < 4; ++d) {
192
+ const int br = bricks_to_base(s, c, d, eb, steel_ok);
193
+ if (br >= 99) continue;
194
+ int len = 0;
195
+ for (int p = c + DIR_DELTA[d]; p != eb && len < 20; p += DIR_DELTA[d]) ++len;
196
+ if (br < best || (br == best && len < blen)) {
197
+ best = br;
198
+ blen = len;
199
+ }
200
+ }
201
+ if (best >= 99) continue;
202
+ // 长度 ≤2 的炮线当回合清弹,能连射;≤4 勉强。更长会把子弹上限锁住,
203
+ // 代价要明显高于多拆一两块砖,搜索才会绕去贴脸的炮位。
204
+ int lane = 0;
205
+ if (blen <= 2) lane = 0;
206
+ else if (blen <= 4) lane = 3;
207
+ else lane = 12 + blen;
208
+ dist[c] = best * 6 + lane;
209
+ }
210
+ }
211
+ }
212
+
213
+ static void build_entry(const State& s, Entry& e) {
214
+ e.hash = s.wall_hash;
215
+ e.valid = true;
216
+ for (int seat = 0; seat < 2; ++seat) {
217
+ seed_attack(s, seat, false, e.atk[seat]);
218
+ spfa(s, e.atk[seat], 4, INF_D);
219
+ seed_attack(s, seat, true, e.atkS[seat]);
220
+ spfa(s, e.atkS[seat], 4, 4);
221
+ }
222
+ for (int k = 0; k < s.n_supply; ++k) {
223
+ for (int c = 0; c < CELLS; ++c) e.sup[k][c] = INF_D;
224
+ e.sup[k][s.supply[k]] = 0;
225
+ spfa(s, e.sup[k], 4, INF_D);
226
+ }
227
+ for (int k = s.n_supply; k < MAX_SUPPLY; ++k)
228
+ for (int c = 0; c < CELLS; ++c) e.sup[k][c] = INF_D;
229
+ }
230
+
231
+ static const Entry& info(const State& s) {
232
+ for (const Entry& e : g_cache)
233
+ if (e.valid && e.hash == s.wall_hash) return e;
234
+ Entry& e = g_cache[g_clock];
235
+ g_clock = (g_clock + 1) % 24;
236
+ build_entry(s, e);
237
+ return e;
238
+ }
239
+
240
+ static int attack_field(const State& s, int seat, int cell, bool steel_ok) {
241
+ const Entry& e = info(s);
242
+ const int v = steel_ok ? e.atkS[seat][cell] : e.atk[seat][cell];
243
+ return v >= INF_D ? 80 : v;
244
+ }
245
+
246
+ // --------------------------------------------------------------------------
247
+ // 现有子弹会不会打中这辆车(其他人原地,没有新炮)。给策略用,搜索本身用 step。
248
+ // --------------------------------------------------------------------------
249
+
250
+ static int bullet_loss(const State& s, int self, Cmd cmd) {
251
+ if (!s.tank[self].alive || s.n_bullets <= 0) return 0;
252
+ int d1 = -1, d2 = -1;
253
+ if (cmd >= 1 && cmd <= 4) d1 = cmd - 1;
254
+ else if (cmd >= 9 && cmd <= 24 && s.P.types[type_of(self)].speed >= 2) {
255
+ d1 = (cmd - 9) >> 2;
256
+ d2 = (cmd - 9) & 3;
257
+ }
258
+ int pos[6];
259
+ bool alive[6];
260
+ for (int i = 0; i < 6; ++i) {
261
+ pos[i] = s.tank[i].pos;
262
+ alive[i] = s.tank[i].alive;
263
+ }
264
+ struct B {
265
+ int p, dir, from;
266
+ bool on;
267
+ };
268
+ B b[MAX_BULLETS];
269
+ const int nb = s.n_bullets;
270
+ for (int k = 0; k < nb; ++k) b[k] = B{s.bullet[k].pos, s.bullet[k].dir, s.bullet[k].pos, true};
271
+
272
+ int loss = 0;
273
+ for (int sub = 0; sub < 2; ++sub) {
274
+ const int dirv = sub == 0 ? d1 : d2;
275
+ int moved_from = -1, moved_to = -1;
276
+ if (dirv >= 0 && alive[self]) {
277
+ const int target = pos[self] + DIR_DELTA[dirv];
278
+ bool occ = false;
279
+ for (int i = 0; i < 6; ++i)
280
+ if (alive[i] && pos[i] == target) occ = true;
281
+ if (!occ && s.grid[target] == EMPTY) {
282
+ moved_from = pos[self];
283
+ moved_to = target;
284
+ pos[self] = target;
285
+ }
286
+ }
287
+ for (int k = 0; k < nb; ++k) {
288
+ if (!b[k].on) continue;
289
+ b[k].from = b[k].p;
290
+ b[k].p += DIR_DELTA[b[k].dir];
291
+ }
292
+ for (int a = 0; a < nb; ++a) {
293
+ if (!b[a].on) continue;
294
+ for (int c = a + 1; c < nb; ++c) {
295
+ if (!b[c].on) continue;
296
+ if (b[c].dir == (b[a].dir ^ 1) && b[a].from == b[c].p && b[c].from == b[a].p)
297
+ b[a].on = b[c].on = false;
298
+ }
299
+ }
300
+ if (moved_from >= 0) {
301
+ for (int k = 0; k < nb; ++k) {
302
+ if (!b[k].on) continue;
303
+ if (b[k].p == moved_from && b[k].from == moved_to) {
304
+ b[k].on = false;
305
+ ++loss;
306
+ }
307
+ }
308
+ }
309
+ for (int k = 0; k < nb; ++k) {
310
+ if (!b[k].on) continue;
311
+ const uint8_t g = s.grid[b[k].p];
312
+ if (g == EMPTY) {
313
+ for (int i = 0; i < 6; ++i) {
314
+ if (alive[i] && pos[i] == b[k].p) {
315
+ b[k].on = false;
316
+ if (i == self) ++loss;
317
+ break;
318
+ }
319
+ }
320
+ } else if (g != WATER) {
321
+ b[k].on = false;
322
+ }
323
+ }
324
+ for (int a = 0; a < nb; ++a) {
325
+ if (!b[a].on) continue;
326
+ for (int c = a + 1; c < nb; ++c) {
327
+ if (b[c].on && b[c].p == b[a].p) b[a].on = b[c].on = false;
328
+ }
329
+ }
330
+ }
331
+ return loss;
332
+ }
333
+
334
+ // 命令走完大概落在哪(别人不动)。开炮和不动停在原地。
335
+ static int dest_of(const State& s, int self, Cmd cmd) {
336
+ const int p0 = s.tank[self].pos;
337
+ int d1 = -1, d2 = -1;
338
+ if (cmd >= 1 && cmd <= 4) d1 = cmd - 1;
339
+ else if (cmd >= 9 && cmd <= 24 && s.P.types[type_of(self)].speed >= 2) {
340
+ d1 = (cmd - 9) >> 2;
341
+ d2 = (cmd - 9) & 3;
342
+ } else {
343
+ return p0;
344
+ }
345
+ auto occ = [&](int cell) {
346
+ for (int i = 0; i < 6; ++i)
347
+ if (i != self && s.tank[i].alive && s.tank[i].pos == cell) return true;
348
+ return false;
349
+ };
350
+ int p = p0;
351
+ if (d1 >= 0) {
352
+ const int n = p + DIR_DELTA[d1];
353
+ if (s.grid[n] == EMPTY && !occ(n)) p = n;
354
+ }
355
+ if (d2 >= 0) {
356
+ const int n = p + DIR_DELTA[d2];
357
+ if (s.grid[n] == EMPTY && !occ(n)) p = n;
358
+ }
359
+ return p;
360
+ }
361
+
362
+ // --------------------------------------------------------------------------
363
+ // 评估。只从 g_me 的视角打分,局面已经是 step 之后的结果。
364
+ // --------------------------------------------------------------------------
365
+
366
+ static int ring_bricks(const State& s, int seat) {
367
+ const int b = s.base_pos[seat];
368
+ const int front = seat == 0 ? UP : DOWN;
369
+ const int cells[5] = {
370
+ b + DIR_DELTA[LEFT],
371
+ b + DIR_DELTA[RIGHT],
372
+ b + DIR_DELTA[front],
373
+ b + DIR_DELTA[front] + DIR_DELTA[LEFT],
374
+ b + DIR_DELTA[front] + DIR_DELTA[RIGHT],
375
+ };
376
+ int n = 0;
377
+ for (int c : cells)
378
+ if (s.grid[c] == BRICK) ++n;
379
+ return n;
380
+ }
381
+
382
+ static Score bullet_future(const State& s) {
383
+ Score sc = 0;
384
+ for (int k = 0; k < s.n_bullets; ++k) {
385
+ const Bullet& b = s.bullet[k];
386
+ int pos = b.pos;
387
+ for (int step = 1; step <= 14; ++step) {
388
+ pos += DIR_DELTA[b.dir];
389
+ const int ti = tank_at(s, pos);
390
+ if (ti >= 0) {
391
+ if (step <= 2) {
392
+ const Tank& t = s.tank[ti];
393
+ Score val = 25000 + Score(t.level) * 20000 + Score(t.hp) * 4000;
394
+ sc += seat_of(ti) == g_me ? -val : val;
395
+ }
396
+ break;
397
+ }
398
+ const uint8_t g = s.grid[pos];
399
+ if (g == EMPTY || g == WATER) continue;
400
+ if (g == BRICK || g == STEEL || g == OUTSIDE) break;
401
+ if (g == BASE0 || g == BASE1) {
402
+ const int bs = g - BASE0;
403
+ Score val = step <= 2 ? 90000 : step <= 4 ? 72000 : step <= 8 ? 42000 : 18000;
404
+ sc += bs == g_me ? -val : val;
405
+ }
406
+ break;
407
+ }
408
+ }
409
+ return sc;
410
+ }
411
+
412
+ static Score tank_value(const State& s, int i) {
413
+ const int seat = seat_of(i);
414
+ const int sgn = seat == g_me ? 1 : -1;
415
+ const Tank& t = s.tank[i];
416
+ if (!t.alive) {
417
+ const int w = respawn_in(s, i);
418
+ return Score(sgn) * -(70000 + 9000 * w);
419
+ }
420
+ const int ty = type_of(i);
421
+ Score v = 80000;
422
+ v += Score(t.hp) * 15000;
423
+ v += Score(t.level) * 70000;
424
+ if (t.level >= 1) v += ty == FIRE ? 28000 : 14000;
425
+ if (t.level >= 2) v += 18000;
426
+ const int f = attack_field(s, seat, t.pos, t.level >= 2);
427
+ const int fc = f > 70 ? 70 : f;
428
+ v += Score(70 - fc) * 700;
429
+
430
+ // 敌人贴着我方基地:最近的一两辆车改去贴他,其余继续打炮。
431
+ bool hunting = false;
432
+ if (seat != g_me) {
433
+ const int bd = manh(t.pos, s.base_pos[g_me]);
434
+ if (bd <= 12) v += Score(13 - bd) * (13 - bd) * 1400;
435
+ } else {
436
+ const Threat th = nearest_threat(s, g_me);
437
+ if (th.id >= 0 && th.base_d <= 10 && !(ty == FIRE && th.base_d > 2)) {
438
+ const int urgency = 11 - th.base_d;
439
+ const int hd = manh(t.pos, th.pos);
440
+ int closer = 0;
441
+ for (int ty2 = 0; ty2 < 3; ++ty2) {
442
+ const int fi = tank_id(g_me, ty2);
443
+ if (fi == i || !s.tank[fi].alive) continue;
444
+ const int od = manh(s.tank[fi].pos, th.pos);
445
+ if (od < hd || (od == hd && fi < i)) ++closer;
446
+ }
447
+ const bool primary = closer == 0 || (closer == 1 && th.base_d <= 5);
448
+ if (primary) {
449
+ hunting = true;
450
+ if (th.base_d <= 6) v -= Score(70 - fc) * 700;
451
+ v += Score(24 - (hd > 24 ? 24 : hd)) * urgency * 1000;
452
+ }
453
+ }
454
+ }
455
+ if (t.level < 2 && !hunting) {
456
+ const Entry& e = info(s);
457
+ const int wait = star_wait(s.turn);
458
+ const bool soon = wait <= 8;
459
+ int best = INF_D;
460
+ int present = INF_D;
461
+ for (int k = 0; k < s.n_supply; ++k) {
462
+ const int d = e.sup[k][t.pos];
463
+ if ((s.star_mask >> k) & 1) {
464
+ if (d < present) present = d;
465
+ } else if (soon && d < best) {
466
+ best = d;
467
+ }
468
+ }
469
+ if (present < INF_D) v += Score(46 - (present > 46 ? 46 : present)) * 650;
470
+ else if (best < INF_D && wait <= 8) {
471
+ const int wgt = wait <= 3 ? 380 : wait <= 6 ? 220 : 120;
472
+ v += Score(46 - (best > 46 ? 46 : best)) * wgt;
473
+ }
474
+ }
475
+ return Score(sgn) * v;
476
+ }
477
+
478
+ static Score evaluate(const State& s) {
479
+ if (s.done) {
480
+ if (s.winner == g_me) return WIN_SC + s.base_hp[g_me];
481
+ if (s.winner < 0) return 0;
482
+ return -WIN_SC - s.base_hp[g_en];
483
+ }
484
+ Score sc = 0;
485
+ sc += Score(s.base_hp[g_me] - s.base_hp[g_en]) * 100000;
486
+ sc += Score(s.kills[g_me] - s.kills[g_en]) * 28000;
487
+ sc += Score(s.stars_eaten[g_me] - s.stars_eaten[g_en]) * 24000;
488
+ sc += Score(ring_bricks(s, g_me) - ring_bricks(s, g_en)) * 16000;
489
+ for (int i = 0; i < 6; ++i) sc += tank_value(s, i);
490
+ sc += bullet_future(s);
491
+ return sc;
492
+ }
493
+
494
+ // --------------------------------------------------------------------------
495
+ // 策略:给一方生成一整套命令。不调用 step,搜索外面才精确推演。
496
+ // --------------------------------------------------------------------------
497
+
498
+ static bool on_hot_line(const State& s, int seat, int cell) {
499
+ const int my_base = s.base_pos[seat];
500
+ for (int ty = 0; ty < 3; ++ty) {
501
+ const int ei = tank_id(1 - seat, ty);
502
+ const Tank& e = s.tank[ei];
503
+ if (!e.alive) continue;
504
+ if (cell_x(e.pos) != cell_x(cell) && cell_y(e.pos) != cell_y(cell)) continue;
505
+ int dir = -1;
506
+ if (cell_x(e.pos) == cell_x(cell)) dir = cell_y(cell) > cell_y(e.pos) ? DOWN : UP;
507
+ else dir = cell_x(cell) > cell_x(e.pos) ? RIGHT : LEFT;
508
+ // 这格是否夹在敌车和我方基地之间,且中间没有钢墙(砖可以)。
509
+ if (cell_x(e.pos) != cell_x(my_base) && cell_y(e.pos) != cell_y(my_base)) continue;
510
+ int bdir = -1;
511
+ if (cell_x(e.pos) == cell_x(my_base)) bdir = cell_y(my_base) > cell_y(e.pos) ? DOWN : UP;
512
+ else bdir = cell_x(my_base) > cell_x(e.pos) ? RIGHT : LEFT;
513
+ if (dir != bdir) continue;
514
+ bool between = false;
515
+ int br = 0;
516
+ bool blocked = false;
517
+ for (int c = e.pos + DIR_DELTA[bdir], n = 0; n < 20; ++n, c += DIR_DELTA[bdir]) {
518
+ if (c == cell) between = true;
519
+ if (c == my_base) break;
520
+ const uint8_t g = s.grid[c];
521
+ if (g == STEEL) {
522
+ blocked = true;
523
+ break;
524
+ }
525
+ if (g == BRICK) ++br;
526
+ else if (g != EMPTY && g != WATER) {
527
+ blocked = true;
528
+ break;
529
+ }
530
+ }
531
+ if (between && !blocked && br <= 1) return true;
532
+ }
533
+ return false;
534
+ }
535
+
536
+ static int exposure(const State& s, int self, int cell) {
537
+ const int seat = seat_of(self);
538
+ const Tank& me = s.tank[self];
539
+ if (!me.alive) return 0;
540
+ int pen = 0;
541
+ const int per = me.hp <= 1 ? 30000 : me.hp == 2 ? 7000 : 3000;
542
+ for (int ty = 0; ty < 3; ++ty) {
543
+ const int ei = tank_id(1 - seat, ty);
544
+ if (!can_fire(s, ei)) continue;
545
+ const Tank& e = s.tank[ei];
546
+ for (int d = 0; d < 4; ++d) {
547
+ int br = 0;
548
+ bool blocked = false;
549
+ int dist = 0;
550
+ for (int c = e.pos + DIR_DELTA[d], n = 1; n <= 12; ++n, c += DIR_DELTA[d]) {
551
+ if (c == cell) {
552
+ dist = n;
553
+ break;
554
+ }
555
+ const int ti = tank_at(s, c);
556
+ if (ti >= 0 && ti != self) {
557
+ blocked = true;
558
+ break;
559
+ }
560
+ const uint8_t g = s.grid[c];
561
+ if (g == EMPTY || g == WATER) continue;
562
+ if (g == BRICK) {
563
+ ++br;
564
+ if (br > 0) {
565
+ blocked = true;
566
+ break;
567
+ }
568
+ } else {
569
+ blocked = true;
570
+ break;
571
+ }
572
+ }
573
+ if (!blocked && dist > 0 && dist <= 8) {
574
+ pen += per - dist * 800;
575
+ break;
576
+ }
577
+ }
578
+ }
579
+ return pen < 0 ? 0 : pen;
580
+ }
581
+
582
+ static int nearest_sup(const State& s, int cell, int& dist_out, bool& present) {
583
+ const Entry& e = info(s);
584
+ int best_k = -1;
585
+ int best_d = INF_D;
586
+ present = false;
587
+ const int wait = star_wait(s.turn);
588
+ for (int k = 0; k < s.n_supply; ++k) {
589
+ const int d = e.sup[k][cell];
590
+ if (d >= INF_D) continue;
591
+ const bool has = (s.star_mask >> k) & 1;
592
+ if (!has && wait > 10) continue;
593
+ int key = d - (has ? 6 : 0);
594
+ if (key < best_d) {
595
+ best_d = key;
596
+ best_k = k;
597
+ dist_out = d;
598
+ present = has;
599
+ }
600
+ }
601
+ if (best_k < 0) dist_out = INF_D;
602
+ return best_k;
603
+ }
604
+
605
+ struct Ctx {
606
+ bool base_claimed;
607
+ bool sup_claimed[MAX_SUPPLY];
608
+ };
609
+
610
+ static int score_cmd(const State& s, int seat, int type, Cmd cmd, Mode mode, const Ctx& ctx) {
611
+ const int self = tank_id(seat, type);
612
+ const Tank& t = s.tank[self];
613
+ if (!t.alive) return cmd == STAY ? 0 : -100000000;
614
+ const bool steel_ok = t.level >= 2;
615
+ const int here = t.pos;
616
+ const int dest = (is_fire(cmd) || cmd == STAY) ? here : dest_of(s, self, cmd);
617
+ const int field_here = attack_field(s, seat, here, steel_ok);
618
+ const int field_dest = attack_field(s, seat, dest, steel_ok);
619
+
620
+ int sc = 0;
621
+ // 火力型 0 级装填为 0,贴脸炮位可以每回合打基地。它的走位优先炮线,星星让给快速型。
622
+ sc -= field_dest * (type == FIRE ? 480 : 220);
623
+
624
+ const int loss = bullet_loss(s, self, cmd);
625
+ if (loss > 0) {
626
+ sc -= loss * 45000;
627
+ if (loss >= t.hp) sc -= 120000 + t.level * 80000;
628
+ }
629
+
630
+ // 站在敌方即将复活的出生点上会被撞毁。
631
+ for (int ty = 0; ty < 3; ++ty) {
632
+ const int ei = tank_id(1 - seat, ty);
633
+ if (!s.tank[ei].alive && respawn_in(s, ei) <= 1 && dest == s.spawn[ei]) sc -= 150000;
634
+ const int fi = tank_id(seat, ty);
635
+ if (fi != self && !s.tank[fi].alive && respawn_in(s, fi) <= 1 && dest == s.spawn[fi]) sc -= 40000;
636
+ }
637
+
638
+ sc -= exposure(s, self, dest) * (mode == M_DEF ? 1 : mode == M_AGG ? 0 : 1);
639
+ // 暗杀模式自己也要躲必死子弹(上面的 loss 已经管了),但不怕「可能被瞄」。
640
+ if (mode == M_AGG) {
641
+ // 把 exposure 加回去一半?上面乘了 0,等于不怕被瞄,专门冲人。保持 0。
642
+ }
643
+
644
+ if (on_hot_line(s, seat, dest)) sc += mode == M_DEF ? 14000 : 6000;
645
+
646
+ if (seat == g_me && g_stuck[type] >= 5 && cmd == STAY) sc -= 6000;
647
+ if (seat == g_me && cmd == g_lastcmd[type] && is_move(cmd)) sc += 200;
648
+
649
+ const bool behind = s.base_hp[seat] + 5 < s.base_hp[1 - seat];
650
+ const bool ahead = s.base_hp[1 - seat] + 6 < s.base_hp[seat];
651
+
652
+ const Threat th = nearest_threat(s, seat);
653
+ bool hunter = false;
654
+ int urgency = 0;
655
+ // 火力型负责连射,除非敌人已经贴到基地上,否则不改去追人。
656
+ if (th.id >= 0 && th.base_d <= 10 && !(type == FIRE && th.base_d > 2)) {
657
+ urgency = 11 - th.base_d;
658
+ const int hd_here = manh(here, th.pos);
659
+ const int hd_dest = manh(dest, th.pos);
660
+ int closer = 0;
661
+ for (int ty2 = 0; ty2 < 3; ++ty2) {
662
+ const int fi = tank_id(seat, ty2);
663
+ if (fi == self || !s.tank[fi].alive) continue;
664
+ const int od = manh(s.tank[fi].pos, th.pos);
665
+ if (od < hd_here || (od == hd_here && fi < self)) ++closer;
666
+ }
667
+ hunter = closer == 0 || (closer == 1 && th.base_d <= 5);
668
+ if (hunter) {
669
+ if (th.base_d <= 6) sc += field_dest * 190;
670
+ sc += (hd_here - hd_dest) * urgency * 900;
671
+ if (hd_dest <= 2 && th.base_d <= 6) sc += 5000 * (3 - hd_dest);
672
+ }
673
+ }
674
+ if (s.P.types[type].speed >= 2 && cmd >= 9 && manh(here, dest) >= 2 && loss == 0) sc += 900;
675
+
676
+ // 已在飞向我方基地的子弹:血量够的车去挡一发(每回合基地最多掉 1)。
677
+ if (!is_fire(cmd) && t.hp >= 2 && loss == 0) {
678
+ const int mb = s.base_pos[seat];
679
+ for (int k = 0; k < s.n_bullets; ++k) {
680
+ const Bullet& b = s.bullet[k];
681
+ if (seat_of(b.owner) == seat) continue;
682
+ int c = b.pos;
683
+ bool on_path = false;
684
+ bool reaches = false;
685
+ for (int n = 0; n < 12; ++n) {
686
+ c += DIR_DELTA[b.dir];
687
+ if (c == dest && c != mb) on_path = true;
688
+ if (c == mb) {
689
+ reaches = true;
690
+ break;
691
+ }
692
+ const uint8_t g = s.grid[c];
693
+ if (g != EMPTY && g != WATER && c != dest) break;
694
+ }
695
+ if (reaches && on_path) sc += 22000;
696
+ }
697
+ }
698
+
699
+ if (is_fire(cmd)) {
700
+ const int dir = cmd - 5;
701
+ const Ray r = trace(s, here, dir, seat);
702
+ if (r.kind == 5) {
703
+ const int tid = r.id;
704
+ if (seat_of(tid) == seat) {
705
+ sc -= 80000;
706
+ } else {
707
+ const Tank& e = s.tank[tid];
708
+ int v = 18000 + e.level * 14000;
709
+ if (e.hp <= 1) v += 22000;
710
+ else if (e.hp == 2) v += 8000;
711
+ const int ef = attack_field(s, 1 - seat, e.pos, e.level >= 2);
712
+ if (ef <= 10) v += 18000; // 对方的炮位车
713
+ const int md = manh(e.pos, s.base_pos[seat]);
714
+ if (md <= 3) v += 70000;
715
+ else if (md <= 6) v += (7 - md) * (7 - md) * 1200;
716
+ if (r.dist <= 2) v += 6000;
717
+ if (mode == M_AGG) v += 22000;
718
+ if (ahead) v += 4000;
719
+ sc += v;
720
+ }
721
+ } else if (r.kind == 3) {
722
+ int v = 56000;
723
+ if (r.dist <= 2) v += 24000;
724
+ else if (r.dist <= 4) v += 14000;
725
+ else if (r.dist >= 8) v -= 10000;
726
+ if (behind) v += 10000;
727
+ if (mode == M_AGG) v -= 6000;
728
+ if (mode == M_STAR) v -= 8000;
729
+ if (ctx.base_claimed) v -= 34000;
730
+ if (ahead && t.hp <= 1) v -= 6000; // 领先就别用脆皮换一发
731
+ if (hunter && th.base_d <= 3 && r.dist > 3) v -= 30000;
732
+ sc += v;
733
+ } else if (r.kind == 4) {
734
+ sc -= 120000;
735
+ } else if (r.kind == 1 || (r.kind == 2 && steel_ok)) {
736
+ const int bc = r.cell;
737
+ const int eb = s.base_pos[1 - seat];
738
+ const int mb = s.base_pos[seat];
739
+ const bool enemy_ring = iabs(cell_x(bc) - cell_x(eb)) + iabs(cell_y(bc) - cell_y(eb)) <= 2 &&
740
+ iabs(cell_x(bc) - cell_x(eb)) <= 1 && iabs(cell_y(bc) - cell_y(eb)) <= 1;
741
+ // 曼哈顿不太准,直接看是不是围墙五格之一。
742
+ int front = seat == 0 ? UP : DOWN; // 这是「我的」前方;敌方基地的前方相反
743
+ (void)front;
744
+ const int efront = (1 - seat) == 0 ? UP : DOWN;
745
+ const int ringc[5] = {
746
+ eb + DIR_DELTA[LEFT],
747
+ eb + DIR_DELTA[RIGHT],
748
+ eb + DIR_DELTA[efront],
749
+ eb + DIR_DELTA[efront] + DIR_DELTA[LEFT],
750
+ eb + DIR_DELTA[efront] + DIR_DELTA[RIGHT],
751
+ };
752
+ bool is_ering = false, is_mring = false;
753
+ const int mfront = seat == 0 ? UP : DOWN;
754
+ const int mring[5] = {
755
+ mb + DIR_DELTA[LEFT],
756
+ mb + DIR_DELTA[RIGHT],
757
+ mb + DIR_DELTA[mfront],
758
+ mb + DIR_DELTA[mfront] + DIR_DELTA[LEFT],
759
+ mb + DIR_DELTA[mfront] + DIR_DELTA[RIGHT],
760
+ };
761
+ for (int c : ringc)
762
+ if (c == bc) is_ering = true;
763
+ for (int c : mring)
764
+ if (c == bc) is_mring = true;
765
+ (void)enemy_ring;
766
+ if (is_mring) sc -= 50000;
767
+ else if (is_ering) sc += 22000;
768
+ else {
769
+ // 这堵墙在不在自己的进攻梯度上:墙格的攻击场比自己低,说明拆了能靠近炮位。
770
+ const int fb = attack_field(s, seat, bc, steel_ok);
771
+ if (fb + 1 < field_here) sc += 6000 + (field_here - fb) * 180;
772
+ else sc += 800;
773
+ }
774
+ } else if (r.kind == 2) {
775
+ sc -= 4000; // 打不掉的钢
776
+ } else {
777
+ sc -= 3500; // 空炮
778
+ }
779
+ // 迎着飞来的子弹开炮,能抵消。
780
+ if (s.n_bullets > 0) {
781
+ const int dirv = cmd - 5;
782
+ for (int k = 0; k < s.n_bullets; ++k) {
783
+ const Bullet& b = s.bullet[k];
784
+ if (b.dir != (dirv ^ 1)) continue;
785
+ if (seat_of(b.owner) == seat) continue;
786
+ bool aligned = false;
787
+ int dist = 0;
788
+ for (int c = here + DIR_DELTA[dirv], n = 1; n <= 8; ++n, c += DIR_DELTA[dirv]) {
789
+ if (c == b.pos) {
790
+ aligned = true;
791
+ dist = n;
792
+ break;
793
+ }
794
+ const uint8_t g = s.grid[c];
795
+ if (g != EMPTY && g != WATER) break;
796
+ }
797
+ if (aligned && dist <= 6) sc += dist <= 3 ? 18000 : 8000;
798
+ }
799
+ }
800
+ } else {
801
+ // 走子或不动:星星梯度。
802
+ if (t.level < 2) {
803
+ int sd = INF_D;
804
+ bool present = false;
805
+ const int k = nearest_sup(s, dest, sd, present);
806
+ if (k >= 0 && sd < INF_D) {
807
+ int w = 0;
808
+ const int wait = star_wait(s.turn);
809
+ if (present) w = 1600;
810
+ else if (wait <= 3) w = 1100;
811
+ else if (wait <= 6) w = 700;
812
+ else if (wait <= 10) w = 280;
813
+ if (type == FAST && t.level < 2) w = w * 2;
814
+ if (mode == M_STAR) w = w * 3 / 2;
815
+ if (mode == M_AGG) w = w / 3;
816
+ if (mode == M_DEF) w = w / 2;
817
+ if (field_here <= 8 && mode != M_STAR && type != FAST) w /= 3;
818
+ if (s.base_hp[1 - seat] <= 6) w /= 3;
819
+ if (ctx.sup_claimed[k]) w /= 4;
820
+ sc += (42 - (sd > 42 ? 42 : sd)) * w / 3;
821
+ if (present && sd == 0) sc += mode == M_AGG ? 8000 : 22000;
822
+ }
823
+ }
824
+ // 已经站在绝佳炮位就别闲逛,除非在躲弹(loss 已经另算)。
825
+ if (cmd == STAY && field_here <= 6) sc += 2500;
826
+ }
827
+ return sc;
828
+ }
829
+
830
+ static Orders make_policy(const State& s, int seat, Mode mode) {
831
+ Orders o;
832
+ int order[3] = {FIRE, ARMOR, FAST};
833
+ if (mode == M_STAR) {
834
+ order[0] = FAST;
835
+ order[1] = FIRE;
836
+ order[2] = ARMOR;
837
+ }
838
+ // 会被现有子弹打死的车先选,免得把「打基地」名额占掉。
839
+ auto lethal = [&](int ty) {
840
+ const int i = tank_id(seat, ty);
841
+ if (!s.tank[i].alive) return false;
842
+ const int loss = bullet_loss(s, i, STAY);
843
+ return loss >= s.tank[i].hp && loss > 0;
844
+ };
845
+ for (int a = 0; a < 3; ++a) {
846
+ if (!lethal(order[a])) continue;
847
+ const int v = order[a];
848
+ for (int b = a; b > 0; --b) order[b] = order[b - 1];
849
+ order[0] = v;
850
+ }
851
+ Ctx ctx{};
852
+ ctx.base_claimed = false;
853
+ for (int k = 0; k < MAX_SUPPLY; ++k) ctx.sup_claimed[k] = false;
854
+
855
+ for (int n = 0; n < 3; ++n) {
856
+ const int ty = order[n];
857
+ const int self = tank_id(seat, ty);
858
+ if (!s.tank[self].alive) {
859
+ o.cmd[ty] = STAY;
860
+ continue;
861
+ }
862
+ Cmd list[N_CMDS];
863
+ const int nc = legal_commands(s, self, list);
864
+ int best_sc = -2000000000;
865
+ Cmd best = STAY;
866
+ for (int i = 0; i < nc; ++i) {
867
+ const int sc = score_cmd(s, seat, ty, list[i], mode, ctx);
868
+ if (sc > best_sc) {
869
+ best_sc = sc;
870
+ best = list[i];
871
+ }
872
+ }
873
+ o.cmd[ty] = best;
874
+ if (is_fire(best)) {
875
+ const Ray r = trace(s, s.tank[self].pos, best - 5, seat);
876
+ if (r.kind == 3) ctx.base_claimed = true;
877
+ } else if (s.tank[self].level < 2) {
878
+ int sd = INF_D;
879
+ bool present = false;
880
+ const int dest = dest_of(s, self, best);
881
+ const int k = nearest_sup(s, dest, sd, present);
882
+ if (k >= 0 && sd <= 8) ctx.sup_claimed[k] = true;
883
+ }
884
+ }
885
+ return o;
886
+ }
887
+
888
+ static Orders tilt_fire(const State& s, int seat, Orders base, int want) {
889
+ // want 1: 能打到敌方基地就打。want 2: 能打到敌方坦克就打。
890
+ for (int ty = 0; ty < 3; ++ty) {
891
+ const int self = tank_id(seat, ty);
892
+ if (!can_fire(s, self)) continue;
893
+ const int pos = s.tank[self].pos;
894
+ for (int d = 0; d < 4; ++d) {
895
+ const Ray r = trace(s, pos, d, seat);
896
+ if (want == 1 && r.kind == 3) {
897
+ base.cmd[ty] = cmd_fire(d);
898
+ break;
899
+ }
900
+ if (want == 2 && r.kind == 5 && seat_of(r.id) != seat) {
901
+ base.cmd[ty] = cmd_fire(d);
902
+ break;
903
+ }
904
+ }
905
+ }
906
+ return base;
907
+ }
908
+
909
+ // 贴脸炮线推进:同一回合只派一辆车打基地(基地每回合最多掉 1),其余沿攻击场靠近短炮线。
910
+ // fast_star 时,还没满级的快速型改去吃星星。
911
+ static Orders rush_base(const State& s, int seat, bool fast_star) {
912
+ Orders o;
913
+ bool assigned[3] = {false, false, false};
914
+ const int target = s.base_pos[1 - seat];
915
+ for (int ty = 0; ty < 3; ++ty) o.cmd[ty] = STAY;
916
+
917
+ if (fast_star) {
918
+ const int self = tank_id(seat, FAST);
919
+ if (s.tank[self].alive && s.tank[self].level < 2 &&
920
+ (s.star_mask != 0 || star_wait(s.turn) <= 8)) {
921
+ Cmd list[N_CMDS];
922
+ const int nc = legal_commands(s, self, list);
923
+ int best_sc = -1000000000;
924
+ Cmd best = STAY;
925
+ for (int i = 0; i < nc; ++i) {
926
+ if (is_fire(list[i])) continue;
927
+ const int dest = dest_of(s, self, list[i]);
928
+ int sd = INF_D;
929
+ bool present = false;
930
+ nearest_sup(s, dest, sd, present);
931
+ int sc = -(sd >= INF_D ? 80 : sd) * 200;
932
+ if (present && sd == 0) sc += 8000;
933
+ const int loss = bullet_loss(s, self, list[i]);
934
+ if (loss >= s.tank[self].hp && loss > 0) sc -= 80000;
935
+ if (sc > best_sc) {
936
+ best_sc = sc;
937
+ best = list[i];
938
+ }
939
+ }
940
+ o.cmd[FAST] = best;
941
+ assigned[FAST] = true;
942
+ }
943
+ }
944
+
945
+ int best_ty = -1, best_d = -1, best_br = 99, best_len = 99;
946
+ for (int ty = 0; ty < 3; ++ty) {
947
+ if (assigned[ty]) continue;
948
+ const int self = tank_id(seat, ty);
949
+ if (!s.tank[self].alive || !can_fire(s, self)) continue;
950
+ const int pos = s.tank[self].pos;
951
+ const bool steel = s.tank[self].level >= 2;
952
+ for (int d = 0; d < 4; ++d) {
953
+ const int br = bricks_to_base(s, pos, d, target, steel);
954
+ if (br > 1) continue;
955
+ const Ray r = trace(s, pos, d, seat);
956
+ if (r.kind == 4) continue;
957
+ if (r.kind == 5 && seat_of(r.id) == seat) continue;
958
+ int len = 0;
959
+ for (int p = pos + DIR_DELTA[d]; p != target && len < 20; p += DIR_DELTA[d]) ++len;
960
+ const bool prefer_fire = (br == best_br && len == best_len && ty == FIRE && best_ty != FIRE);
961
+ if (br < best_br || (br == best_br && len < best_len) || prefer_fire) {
962
+ best_br = br;
963
+ best_len = len;
964
+ best_d = d;
965
+ best_ty = ty;
966
+ }
967
+ }
968
+ }
969
+ if (best_ty >= 0) {
970
+ o.cmd[best_ty] = cmd_fire(best_d);
971
+ assigned[best_ty] = true;
972
+ }
973
+
974
+ for (int ty = 0; ty < 3; ++ty) {
975
+ if (assigned[ty]) continue;
976
+ const int self = tank_id(seat, ty);
977
+ if (!s.tank[self].alive) continue;
978
+ const int pos = s.tank[self].pos;
979
+ const bool steel = s.tank[self].level >= 2;
980
+ Cmd list[N_CMDS];
981
+ const int nc = legal_commands(s, self, list);
982
+ int best_sc = -1000000000;
983
+ Cmd best = STAY;
984
+ const int here_f = attack_field(s, seat, pos, steel);
985
+ for (int i = 0; i < nc; ++i) {
986
+ if (is_fire(list[i])) continue;
987
+ const int dest = dest_of(s, self, list[i]);
988
+ const int dest_f = attack_field(s, seat, dest, steel);
989
+ int sc = (here_f - dest_f) * 1000;
990
+ const int loss = bullet_loss(s, self, list[i]);
991
+ if (loss >= s.tank[self].hp && loss > 0) sc -= 80000;
992
+ else sc -= loss * 5000;
993
+ if (sc > best_sc) {
994
+ best_sc = sc;
995
+ best = list[i];
996
+ }
997
+ }
998
+ o.cmd[ty] = best;
999
+ }
1000
+ return o;
1001
+ }
1002
+
1003
+ // --------------------------------------------------------------------------
1004
+ // 决策
1005
+ // --------------------------------------------------------------------------
1006
+
1007
+ static bool same_ord(const Orders& a, const Orders& b) {
1008
+ return a.cmd[0] == b.cmd[0] && a.cmd[1] == b.cmd[1] && a.cmd[2] == b.cmd[2];
1009
+ }
1010
+
1011
+ static Score rollout(State st, int plies) {
1012
+ for (int p = 0; p < plies && !st.done; ++p) {
1013
+ if (!time_ok(3.2)) break;
1014
+ const Orders my = make_policy(st, g_me, M_BAL);
1015
+ const Orders en = make_policy(st, g_en, M_BAL);
1016
+ step_as(st, g_me, my, en);
1017
+ ++g_nodes;
1018
+ }
1019
+ return evaluate(st);
1020
+ }
1021
+
1022
+ static Score vs_list(const State& s, const Orders& mine, const Orders* ens, int ne, int plies) {
1023
+ Score worst = WIN_SC;
1024
+ for (int j = 0; j < ne; ++j) {
1025
+ if (!time_ok(3.0)) return -WIN_SC; // 没比完,不能拿来当最好
1026
+ State st = s;
1027
+ step_as(st, g_me, mine, ens[j]);
1028
+ ++g_nodes;
1029
+ const Score sc = plies <= 0 ? evaluate(st) : rollout(st, plies);
1030
+ if (sc < worst) worst = sc;
1031
+ }
1032
+ return worst;
1033
+ }
1034
+
1035
+ void init(const State& s, int me) {
1036
+ (void)s;
1037
+ g_me = me;
1038
+ g_en = 1 - me;
1039
+ for (int i = 0; i < 3; ++i) {
1040
+ g_stuck[i] = 0;
1041
+ g_lastpos[i] = -1;
1042
+ g_lastcmd[i] = STAY;
1043
+ }
1044
+ for (Entry& e : g_cache) e.valid = false;
1045
+ g_clock = 0;
1046
+ g_nodes = 0;
1047
+ g_stop = false;
1048
+ }
1049
+
1050
+ Orders decide(const State& s, int me) {
1051
+ g_me = me;
1052
+ g_en = 1 - me;
1053
+ g_nodes = 0;
1054
+ g_stop = false;
1055
+ for (int ty = 0; ty < 3; ++ty) {
1056
+ const Tank& t = s.tank[tank_id(me, ty)];
1057
+ if (!t.alive) {
1058
+ g_stuck[ty] = 0;
1059
+ g_lastpos[ty] = -1;
1060
+ continue;
1061
+ }
1062
+ g_stuck[ty] = (t.pos == g_lastpos[ty]) ? g_stuck[ty] + 1 : 0;
1063
+ g_lastpos[ty] = t.pos;
1064
+ }
1065
+
1066
+ // 先把距离场建好,避免枚举中途卡在第一次填充上。
1067
+ (void)info(s);
1068
+
1069
+ Orders mine_pol[M_N];
1070
+ for (int m = 0; m < M_N; ++m) mine_pol[m] = make_policy(s, me, Mode(m));
1071
+
1072
+ Orders ens[12];
1073
+ int ne = 0;
1074
+ auto push_en = [&](Orders o) {
1075
+ for (int i = 0; i < ne; ++i)
1076
+ if (same_ord(ens[i], o)) return;
1077
+ if (ne < 12) ens[ne++] = o;
1078
+ };
1079
+ Orders en_pol[M_N];
1080
+ for (int m = 0; m < M_N; ++m) {
1081
+ en_pol[m] = make_policy(s, g_en, Mode(m));
1082
+ push_en(en_pol[m]);
1083
+ }
1084
+ push_en(Orders{});
1085
+ push_en(tilt_fire(s, g_en, en_pol[M_BAL], 1));
1086
+ push_en(tilt_fire(s, g_en, en_pol[M_AGG], 2));
1087
+ const Orders rush_en = rush_base(s, g_en, false);
1088
+ push_en(rush_en);
1089
+
1090
+ // 一回合枚举用的三个威胁:暗杀、朝基地推进、原地。
1091
+ const Orders trio[3] = {
1092
+ en_pol[M_AGG],
1093
+ rush_en,
1094
+ Orders{},
1095
+ };
1096
+
1097
+ Cmd c0[N_CMDS], c1[N_CMDS], c2[N_CMDS];
1098
+ const int n0 = legal_commands(s, tank_id(me, FAST), c0);
1099
+ const int n1 = legal_commands(s, tank_id(me, FIRE), c1);
1100
+ const int n2 = legal_commands(s, tank_id(me, ARMOR), c2);
1101
+
1102
+ constexpr int TOPN = 16;
1103
+ Orders top_o[TOPN];
1104
+ Score top_s[TOPN];
1105
+ int nt = 0;
1106
+ auto consider = [&](const Orders& o, Score sc) {
1107
+ if (nt < TOPN) {
1108
+ top_o[nt] = o;
1109
+ top_s[nt] = sc;
1110
+ ++nt;
1111
+ return;
1112
+ }
1113
+ int w = 0;
1114
+ for (int i = 1; i < nt; ++i)
1115
+ if (top_s[i] < top_s[w]) w = i;
1116
+ if (sc > top_s[w]) {
1117
+ top_s[w] = sc;
1118
+ top_o[w] = o;
1119
+ }
1120
+ };
1121
+
1122
+ bool enum_done = true;
1123
+ for (int i = 0; i < n0 && enum_done; ++i) {
1124
+ for (int j = 0; j < n1 && enum_done; ++j) {
1125
+ for (int k = 0; k < n2; ++k) {
1126
+ if (!time_ok(16.0)) {
1127
+ enum_done = false;
1128
+ break;
1129
+ }
1130
+ Orders o;
1131
+ o.cmd[FAST] = c0[i];
1132
+ o.cmd[FIRE] = c1[j];
1133
+ o.cmd[ARMOR] = c2[k];
1134
+ Score worst = WIN_SC;
1135
+ for (int t = 0; t < 3; ++t) {
1136
+ State st = s;
1137
+ step_as(st, me, o, trio[t]);
1138
+ ++g_nodes;
1139
+ const Score sc = evaluate(st);
1140
+ if (sc < worst) worst = sc;
1141
+ }
1142
+ consider(o, worst);
1143
+ }
1144
+ }
1145
+ }
1146
+
1147
+ Orders fins[24];
1148
+ int nf = 0;
1149
+ auto push_fin = [&](Orders o) {
1150
+ for (int i = 0; i < nf; ++i)
1151
+ if (same_ord(fins[i], o)) return;
1152
+ if (nf < 24) fins[nf++] = o;
1153
+ };
1154
+ // 一回合里看起来最好的排前面,时间不够时至少它们被完整看过。
1155
+ int idx[TOPN];
1156
+ for (int i = 0; i < nt; ++i) idx[i] = i;
1157
+ for (int a = 0; a < nt; ++a) {
1158
+ int b = a;
1159
+ for (int c = a + 1; c < nt; ++c)
1160
+ if (top_s[idx[c]] > top_s[idx[b]]) b = c;
1161
+ const int tmp = idx[a];
1162
+ idx[a] = idx[b];
1163
+ idx[b] = tmp;
1164
+ }
1165
+ for (int i = 0; i < nt; ++i) push_fin(top_o[idx[i]]);
1166
+ for (int m = 0; m < M_N; ++m) push_fin(mine_pol[m]);
1167
+ push_fin(rush_base(s, me, true));
1168
+
1169
+ const double left0 = budget_left();
1170
+ int plies = 7;
1171
+ if (left0 > 600) plies = 14;
1172
+ else if (left0 > 80) plies = 10;
1173
+ else if (left0 < 22) plies = 3;
1174
+ else if (left0 < 35) plies = 5;
1175
+
1176
+ // 终选时用全部对手模型,但时间紧就只留前几个最危险的。
1177
+ int use_ne = ne;
1178
+ if (left0 < 28) use_ne = 3;
1179
+ else if (left0 < 45) use_ne = 5;
1180
+
1181
+ Score best_sc = -WIN_SC;
1182
+ Orders best = mine_pol[M_BAL];
1183
+ bool any = false;
1184
+ for (int i = 0; i < nf; ++i) {
1185
+ if (budget_left() < 6.0 && any) break;
1186
+ #if !FIXED_NODES
1187
+ if (g_stop && any) break;
1188
+ #endif
1189
+ const int use_plies = (budget_left() < 12.0 && any) ? 0 : plies;
1190
+ const Score sc = vs_list(s, fins[i], ens, use_ne, use_plies);
1191
+ if (!any || sc > best_sc) {
1192
+ best_sc = sc;
1193
+ best = fins[i];
1194
+ any = true;
1195
+ }
1196
+ }
1197
+
1198
+ for (int ty = 0; ty < 3; ++ty) g_lastcmd[ty] = best.cmd[ty];
1199
+
1200
+ std::fprintf(stderr, "t=%d me=%d n=%ld left=%.1f plies=%d fins=%d ens=%d sc=%lld [%s %s %s]\n",
1201
+ int(s.turn), me, g_nodes, budget_left(), plies, nf, use_ne,
1202
+ (long long)best_sc, cmd_text(best.cmd[0]).c_str(), cmd_text(best.cmd[1]).c_str(),
1203
+ cmd_text(best.cmd[2]).c_str());
1204
+ return best;
1205
+ }