dsh-eval-guard 0.2.3 → 0.3.1

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 (221) hide show
  1. package/README.md +91 -13
  2. package/assets/commit0/instance.json +30 -0
  3. package/assets/commit0/workspace.zip +0 -0
  4. package/assets/featurebench/hidden/test_regression.py +690 -0
  5. package/assets/featurebench/instance.json +16 -0
  6. package/assets/featurebench/workspace.zip +0 -0
  7. package/assets/swe-bench-pro/hidden/test.patch +36 -0
  8. package/assets/swe-bench-pro/instance.json +10 -0
  9. package/assets/swe-bench-pro/workspace.zip +0 -0
  10. package/eval/README.md +18 -0
  11. package/eval/arena/arena/README.md +21 -0
  12. package/eval/arena/arena/bt.py +104 -0
  13. package/eval/arena/arena/clock.py +15 -0
  14. package/eval/arena/arena/core.py +265 -0
  15. package/eval/arena/arena/run.cmd +24 -0
  16. package/eval/arena/arena/run.sh +11 -0
  17. package/eval/arena/arena/schedule.py +79 -0
  18. package/eval/arena/arena/tournament.py +183 -0
  19. package/eval/arena/bomberman/bots/README.md +16 -0
  20. package/eval/arena/bomberman/bots/deepseek-v4.1-flash.cpp +447 -0
  21. package/eval/arena/bomberman/bots/doubao-seed-2.1.cpp +542 -0
  22. package/eval/arena/bomberman/bots/glm-5.3.cpp +406 -0
  23. package/eval/arena/bomberman/bots/gpt-6-astra.cpp +768 -0
  24. package/eval/arena/bomberman/bots/gpt-6-sol.cpp +444 -0
  25. package/eval/arena/bomberman/bots/kimi-k3.cpp +432 -0
  26. package/eval/arena/bomberman/bots/mimo-v2.6-pro.cpp +529 -0
  27. package/eval/arena/bomberman/bots/opus-5.5.cpp +548 -0
  28. package/eval/arena/bomberman/bots/qwen-3.8-max.cpp +836 -0
  29. package/eval/arena/bomberman/tournament/README.md +11 -0
  30. package/eval/arena/bomberman/tournament/bt.py +56 -0
  31. package/eval/arena/bomberman/tournament/evaluate.py +199 -0
  32. package/eval/arena/bomberman/tournament/run.py +79 -0
  33. package/eval/arena/bomberman/tournament/schedule-2to10.json.gz +0 -0
  34. package/eval/arena/bomberman/workspace/MANIFEST.json +13 -0
  35. package/eval/arena/bomberman/workspace/README.md +29 -0
  36. package/eval/arena/bomberman/workspace/RULES.md +100 -0
  37. package/eval/arena/bomberman/workspace/baseline.cpp +240 -0
  38. package/eval/arena/bomberman/workspace/clock.py +25 -0
  39. package/eval/arena/bomberman/workspace/engine/arena.py +198 -0
  40. package/eval/arena/bomberman/workspace/engine/build.py +35 -0
  41. package/eval/arena/bomberman/workspace/engine/engine.py +256 -0
  42. package/eval/arena/bomberman/workspace/engine/verify_replay.py +24 -0
  43. package/eval/arena/bomberman/workspace/run.cmd +28 -0
  44. package/eval/arena/bomberman/workspace/run.sh +16 -0
  45. package/eval/arena/bomberman/workspace/viewer/index.html +42 -0
  46. package/eval/arena/ctf/bots/README.md +18 -0
  47. package/eval/arena/ctf/bots/deepseek-v4.1-flash.cpp +601 -0
  48. package/eval/arena/ctf/bots/doubao-seed-2.1-turbo.cpp +320 -0
  49. package/eval/arena/ctf/bots/fable-5.1.cpp +634 -0
  50. package/eval/arena/ctf/bots/glm-5.3.cpp +689 -0
  51. package/eval/arena/ctf/bots/gpt-6-astra.cpp +657 -0
  52. package/eval/arena/ctf/bots/gpt-6.1-sol.cpp +725 -0
  53. package/eval/arena/ctf/bots/kimi-k3.cpp +667 -0
  54. package/eval/arena/ctf/bots/mimo-v2.6-pro.cpp +533 -0
  55. package/eval/arena/ctf/bots/opus-5.5.cpp +645 -0
  56. package/eval/arena/ctf/bots/qwen-3.8-max.cpp +726 -0
  57. package/eval/arena/ctf/bots/sonnet-5.5.cpp +569 -0
  58. package/eval/arena/ctf/tournament/README.md +15 -0
  59. package/eval/arena/ctf/tournament/formal-2to8.json +31 -0
  60. package/eval/arena/ctf/tournament/formal-9to15.json +31 -0
  61. package/eval/arena/ctf/workspace/README.md +41 -0
  62. package/eval/arena/ctf/workspace/RULES.md +119 -0
  63. package/eval/arena/ctf/workspace/arena.py +186 -0
  64. package/eval/arena/ctf/workspace/baseline.cpp +257 -0
  65. package/eval/arena/ctf/workspace/clock.py +15 -0
  66. package/eval/arena/ctf/workspace/core.py +265 -0
  67. package/eval/arena/ctf/workspace/engine.py +389 -0
  68. package/eval/arena/ctf/workspace/run.cmd +24 -0
  69. package/eval/arena/ctf/workspace/run.sh +11 -0
  70. package/eval/arena/ctf/workspace/viewer.html +71 -0
  71. package/eval/arena/land/bots/README.md +17 -0
  72. package/eval/arena/land/bots/deepseek-4.1f@workbuddy#1/bot.cpp +608 -0
  73. package/eval/arena/land/bots/glm-5.3@workbuddy#1/bot.cpp +521 -0
  74. package/eval/arena/land/bots/gpt-6-astra@codex#1/bot.cpp +373 -0
  75. package/eval/arena/land/bots/gpt-6-sol@codex#1/bot.cpp +341 -0
  76. package/eval/arena/land/bots/kimi-k3@workbuddy#1/bot.cpp +492 -0
  77. package/eval/arena/land/bots/mimo-2.6-pro@zcode#1/bot.cpp +652 -0
  78. package/eval/arena/land/bots/mystery-model@zcode#1/bot.cpp +466 -0
  79. package/eval/arena/land/bots/opus-5.5@claude-code#1/bot.cpp +630 -0
  80. package/eval/arena/land/bots/sonnet-5@claude-code#1/bot.cpp +619 -0
  81. package/eval/arena/land/tournament/make_replay_page.py +17 -0
  82. package/eval/arena/land/tournament/rate.py +290 -0
  83. package/eval/arena/land/tournament/round_robin.py +172 -0
  84. package/eval/arena/land/tournament/sparring.cpp +356 -0
  85. package/eval/arena/land/workspace/RULES.md +114 -0
  86. package/eval/arena/land/workspace/TASK.md +46 -0
  87. package/eval/arena/land/workspace/anchors/.build/greedy_f2e6d2dea5ca +0 -0
  88. package/eval/arena/land/workspace/anchors/greedy.cpp +194 -0
  89. package/eval/arena/land/workspace/anchors/random.cpp +80 -0
  90. package/eval/arena/land/workspace/clock.py +64 -0
  91. package/eval/arena/land/workspace/engine/build.py +33 -0
  92. package/eval/arena/land/workspace/engine/game.py +273 -0
  93. package/eval/arena/land/workspace/engine/referee.py +308 -0
  94. package/eval/arena/land/workspace/starter/.build/bot_be32b45b4699 +0 -0
  95. package/eval/arena/land/workspace/starter/bot.cpp +82 -0
  96. package/eval/arena/land/workspace/viewer/viewer.html +210 -0
  97. package/eval/arena/tank/bots/README.md +24 -0
  98. package/eval/arena/tank/bots/half1/deepseek-v4.1-flash.cpp +367 -0
  99. package/eval/arena/tank/bots/half1/doubao-seed-evolving.cpp +402 -0
  100. package/eval/arena/tank/bots/half1/gemini-4-argon.cpp +1132 -0
  101. package/eval/arena/tank/bots/half1/glm-5.3.cpp +551 -0
  102. package/eval/arena/tank/bots/half1/gpt-6-astra.cpp +233 -0
  103. package/eval/arena/tank/bots/half1/gpt-6.1-sol-ultrafast.cpp +350 -0
  104. package/eval/arena/tank/bots/half1/gpt-6.1-sol.cpp +78 -0
  105. package/eval/arena/tank/bots/half1/grok-4.7.cpp +1099 -0
  106. package/eval/arena/tank/bots/half1/haiku-5.5.cpp +210 -0
  107. package/eval/arena/tank/bots/half1/kimi-k3.cpp +353 -0
  108. package/eval/arena/tank/bots/half1/mimo-v2.6-pro.cpp +336 -0
  109. package/eval/arena/tank/bots/half1/muse-wonder-full.cpp +560 -0
  110. package/eval/arena/tank/bots/half1/opus-5.5.cpp +435 -0
  111. package/eval/arena/tank/bots/half1/qwen-3.8-max.cpp +537 -0
  112. package/eval/arena/tank/bots/half2/deepseek-v4.1-flash.cpp +425 -0
  113. package/eval/arena/tank/bots/half2/doubao-seed-evolving.cpp +308 -0
  114. package/eval/arena/tank/bots/half2/gemini-4-argon.cpp +1390 -0
  115. package/eval/arena/tank/bots/half2/glm-5.3.cpp +651 -0
  116. package/eval/arena/tank/bots/half2/gpt-6-astra.cpp +241 -0
  117. package/eval/arena/tank/bots/half2/gpt-6.1-sol-ultrafast.cpp +356 -0
  118. package/eval/arena/tank/bots/half2/gpt-6.1-sol.cpp +87 -0
  119. package/eval/arena/tank/bots/half2/grok-4.7.cpp +1205 -0
  120. package/eval/arena/tank/bots/half2/haiku-5.5.cpp +210 -0
  121. package/eval/arena/tank/bots/half2/kimi-k3.cpp +487 -0
  122. package/eval/arena/tank/bots/half2/mimo-v2.6-pro.cpp +336 -0
  123. package/eval/arena/tank/bots/half2/muse-wonder-full.cpp +560 -0
  124. package/eval/arena/tank/bots/half2/opus-5.5.cpp +476 -0
  125. package/eval/arena/tank/bots/half2/qwen-3.8-max.cpp +392 -0
  126. package/eval/arena/tank/tournament/README.md +24 -0
  127. package/eval/arena/tank/tournament/bt.py +218 -0
  128. package/eval/arena/tank/tournament/final.json +202 -0
  129. package/eval/arena/tank/tournament/halftime.json +146 -0
  130. package/eval/arena/tank/tournament/progress.json +911 -0
  131. package/eval/arena/tank/tournament/schedule.py +100 -0
  132. package/eval/arena/tank/tournament/sparring.cpp +928 -0
  133. package/eval/arena/tank/tournament/tournament.py +1426 -0
  134. package/eval/arena/tank/workspace/README.md +169 -0
  135. package/eval/arena/tank/workspace/RULES.md +443 -0
  136. package/eval/arena/tank/workspace/arena.py +862 -0
  137. package/eval/arena/tank/workspace/baseline.cpp +259 -0
  138. package/eval/arena/tank/workspace/clock.py +103 -0
  139. package/eval/arena/tank/workspace/core.py +712 -0
  140. package/eval/arena/tank/workspace/engine.py +573 -0
  141. package/eval/arena/tank/workspace/history/.lock +0 -0
  142. package/eval/arena/tank/workspace/history/20261017-193610-08715287f91f-bot.cpp +109 -0
  143. package/eval/arena/tank/workspace/history/20261017-193619-f00b2d5635fa-gpt-6-astra.cpp +241 -0
  144. package/eval/arena/tank/workspace/history/20261017-193643-63319e0a2b1a-gpt-6_1-sol-ultrafast.cpp +356 -0
  145. package/eval/arena/tank/workspace/history/20261017-193647-41c1341d0a8a-gemini-4-argon.cpp +1390 -0
  146. package/eval/arena/tank/workspace/history/20261017-193652-e34c0f45539a-gpt-6_1-sol.cpp +87 -0
  147. package/eval/arena/tank/workspace/history/20261017-193654-c026348ce96e-opus-5_5.cpp +476 -0
  148. package/eval/arena/tank/workspace/history/20261017-193655-4a21366e94d1-muse-wonder-full.cpp +560 -0
  149. package/eval/arena/tank/workspace/history/20261017-193657-dbc4e9969c52-mimo-v2_6-pro.cpp +336 -0
  150. package/eval/arena/tank/workspace/history/20261017-193703-723cd036eb31-grok-4_7.cpp +1205 -0
  151. package/eval/arena/tank/workspace/history/20261017-193707-1ebbb227be68-deepseek-v4_1-flash.cpp +425 -0
  152. package/eval/arena/tank/workspace/history/20261017-193709-aeb0e7202ad5-doubao-seed-evolving.cpp +308 -0
  153. package/eval/arena/tank/workspace/history/20261017-193710-b21ebeb9bc76-kimi-k3.cpp +487 -0
  154. package/eval/arena/tank/workspace/history/20261017-193712-54cd86531905-haiku-5_5.cpp +210 -0
  155. package/eval/arena/tank/workspace/history/20261017-193714-efdb8e35c0bf-qwen-3_8-max.cpp +392 -0
  156. package/eval/arena/tank/workspace/history/20261017-193715-681423c23fdb-glm-5_3.cpp +651 -0
  157. package/eval/arena/tank/workspace/history/20261017-193717-1a8b3c75181d-sparring.cpp +928 -0
  158. package/eval/arena/tank/workspace/history/20261017-193720-1e0803a541e8-baseline.cpp +259 -0
  159. package/eval/arena/tank/workspace/history/log.jsonl +103 -0
  160. package/eval/arena/tank/workspace/judge/cycles.hpp +53 -0
  161. package/eval/arena/tank/workspace/judge/fast.cpp +337 -0
  162. package/eval/arena/tank/workspace/judge/referee.cpp +125 -0
  163. package/eval/arena/tank/workspace/judge/speed.cpp +99 -0
  164. package/eval/arena/tank/workspace/run.cmd +31 -0
  165. package/eval/arena/tank/workspace/run.sh +23 -0
  166. package/eval/arena/tank/workspace/tank.hpp +1762 -0
  167. package/eval/arena/tank/workspace/tank_main.cpp +215 -0
  168. package/eval/arena/tank/workspace/viewer.html +1150 -0
  169. package/eval/arena/warehouse/workspace/SPEC.md +130 -0
  170. package/eval/arena/warehouse/workspace/TASK.md +71 -0
  171. package/eval/arena/warehouse/workspace/baseline/common.h +88 -0
  172. package/eval/arena/warehouse/workspace/baseline/main.cpp +27 -0
  173. package/eval/arena/warehouse/workspace/baseline/old_stuff.cpp +87 -0
  174. package/eval/arena/warehouse/workspace/baseline/sim.cpp +427 -0
  175. package/eval/arena/warehouse/workspace/baseline/util.cpp +220 -0
  176. package/eval/arena/warehouse/workspace/clock.py +42 -0
  177. package/eval/arena/warehouse/workspace/src/common.h +88 -0
  178. package/eval/arena/warehouse/workspace/src/main.cpp +27 -0
  179. package/eval/arena/warehouse/workspace/src/old_stuff.cpp +87 -0
  180. package/eval/arena/warehouse/workspace/src/sim.cpp +427 -0
  181. package/eval/arena/warehouse/workspace/src/util.cpp +220 -0
  182. package/eval/arena/warehouse/workspace/tools/bench.py +119 -0
  183. package/eval/arena/warehouse/workspace/tools/check_input.py +161 -0
  184. package/eval/arena/warehouse/workspace/tools/gen.py +132 -0
  185. package/eval/arena/warehouse/workspace/tools/measure.py +107 -0
  186. package/eval/arena/warehouse/workspace/workloads/public/pub_crowded.in +1420 -0
  187. package/eval/arena/warehouse/workspace/workloads/public/pub_crowded.out +9162 -0
  188. package/eval/arena/warehouse/workspace/workloads/public/pub_dynamic.in +1465 -0
  189. package/eval/arena/warehouse/workspace/workloads/public/pub_dynamic.out +3439 -0
  190. package/eval/arena/warehouse/workspace/workloads/public/pub_flood.in +2757 -0
  191. package/eval/arena/warehouse/workspace/workloads/public/pub_flood.out +3126 -0
  192. package/eval/arena/warehouse/workspace/workloads/public/pub_small.in +731 -0
  193. package/eval/arena/warehouse/workspace/workloads/public/pub_small.out +2578 -0
  194. package/eval/arena/warehouse/workspace/workloads/public/pub_wide.in +528 -0
  195. package/eval/arena/warehouse/workspace/workloads/public/pub_wide.out +2026 -0
  196. package/eval/bomberman.py +20 -0
  197. package/eval/commit0.py +21 -0
  198. package/eval/common.py +61 -0
  199. package/eval/ctf.py +20 -0
  200. package/eval/featurebench.py +25 -0
  201. package/eval/land.py +20 -0
  202. package/eval/legacy/bomberman-prices.json +4 -0
  203. package/eval/legacy/bomberman.py +161 -0
  204. package/eval/legacy/commit0.py +91 -0
  205. package/eval/legacy/ctf-prices.json +4 -0
  206. package/eval/legacy/ctf.py +178 -0
  207. package/eval/legacy/featurebench.py +103 -0
  208. package/eval/legacy/land-prices.json +4 -0
  209. package/eval/legacy/land.py +177 -0
  210. package/eval/legacy/swe_bench_pro.py +116 -0
  211. package/eval/legacy/tank-prices.json +3 -0
  212. package/eval/legacy/tank.py +408 -0
  213. package/eval/legacy/warehouse-prices.json +4 -0
  214. package/eval/legacy/warehouse.py +365 -0
  215. package/eval/swe-bench-pro.py +25 -0
  216. package/eval/tank.py +20 -0
  217. package/eval/warehouse.py +20 -0
  218. package/lib/client/index.js +376 -63
  219. package/lib/client/index.js.map +1 -1
  220. package/lib/index.js +684 -27
  221. package/package.json +2 -1
@@ -0,0 +1,1390 @@
1
+ #include "tank.hpp"
2
+ #include <algorithm>
3
+ #include <cmath>
4
+ #include <cstdint>
5
+ #include <cstring>
6
+
7
+ using namespace tank;
8
+
9
+ namespace {
10
+
11
+ constexpr int INF = 1000000;
12
+
13
+ struct MapCache {
14
+ // 3 attack lanes per enemy base: 0=Left flank, 1=Center, 2=Right flank
15
+ int dist_lane[6][3][CELLS];
16
+ // Minimum across all 3 lanes
17
+ int dist_atk[6][CELLS];
18
+ // Distance from tank's current pos (or spawn) to cell c
19
+ int dist_from_tank[6][CELLS];
20
+ // Distance from supply k to cell c
21
+ int dist_supply[MAX_SUPPLY][CELLS];
22
+ // Is cell part of seat's base protective ring/wall?
23
+ bool is_base_shield[2][CELLS];
24
+ int base_ray_dist[2][CELLS];
25
+ int base_ray_dir[2][CELLS]; // direction from c toward base[seat]
26
+ int base_ray_lane[2][CELLS]; // 0=left, 1=center, 2=right, -1=none
27
+
28
+ // Role assignments per tank 0..5
29
+ int assigned_lane[6];
30
+ int assigned_supply[6];
31
+ int assigned_def_enemy[6];
32
+ int assigned_def_urgency[6];
33
+
34
+ // Snapshot of root grid to detect broken walls during lookahead
35
+ uint8_t root_grid[CELLS];
36
+ bool is_atk_wall[2][CELLS];
37
+ };
38
+
39
+ static MapCache G;
40
+ static int last_pos_hist[6][6];
41
+ static int hist_turn = -1;
42
+
43
+ inline int manhattan(int c1, int c2) {
44
+ return std::abs(cell_x(c1) - cell_x(c2)) + std::abs(cell_y(c1) - cell_y(c2));
45
+ }
46
+
47
+ inline bool is_enemy_spawn_soon(const State& s, int seat, int cell) {
48
+ int op = 1 - seat;
49
+ for (int t = 0; t < 3; ++t) {
50
+ int i = tank_id(op, t);
51
+ if (s.spawn[i] == cell && !s.tank[i].alive && respawn_in(s, i) <= 1) {
52
+ return true;
53
+ }
54
+ }
55
+ return false;
56
+ }
57
+
58
+ inline bool is_ally_spawn_soon(const State& s, int seat, int self_id, int cell) {
59
+ for (int t = 0; t < 3; ++t) {
60
+ int i = tank_id(seat, t);
61
+ if (i != self_id && s.spawn[i] == cell && !s.tank[i].alive && respawn_in(s, i) <= 1) {
62
+ return true;
63
+ }
64
+ }
65
+ return false;
66
+ }
67
+
68
+ inline void count_ray_obstacles(const State& s, int seat, int c, int& bricks, int& steels) {
69
+ bricks = 0;
70
+ steels = 0;
71
+ int dist = G.base_ray_dist[seat][c];
72
+ if (dist <= 0) return;
73
+ int dir = G.base_ray_dir[seat][c];
74
+ for (int st = 1; st < dist; ++st) {
75
+ uint8_t g = s.grid[c + st * DIR_DELTA[dir]];
76
+ if (g == BRICK) bricks++;
77
+ else if (g == STEEL) steels++;
78
+ }
79
+ }
80
+
81
+ int enemy_base_threat_level(const State& s, int seat, int e) {
82
+ if (!s.tank[e].alive) return 0;
83
+ int epos = s.tank[e].pos;
84
+ if (G.base_ray_dist[seat][epos] > 0) {
85
+ int bricks = 0, steels = 0;
86
+ count_ray_obstacles(s, seat, epos, bricks, steels);
87
+ bool can_st = (s.tank[e].level >= 2 && s.P.level2_steel);
88
+ if (steels == 0 || can_st) {
89
+ if (bricks == 0 && G.base_ray_dist[seat][epos] <= 7) return 3;
90
+ if (bricks <= 1 && G.base_ray_dist[seat][epos] <= 6) return 3;
91
+ if (bricks <= 2 && G.base_ray_dist[seat][epos] <= 6) return 2;
92
+ }
93
+ }
94
+ int eatk = G.dist_atk[e][epos];
95
+ int ey = cell_y(epos);
96
+ bool in_my_half = (seat == 0) ? (ey >= 7) : (ey <= 7);
97
+ bool deep_in_my_half = (seat == 0) ? (ey >= 10) : (ey <= 4);
98
+ if (eatk <= 7 || deep_in_my_half || manhattan(epos, s.base_pos[seat]) <= 5) return 3;
99
+ if (eatk <= 14 || in_my_half) return 2;
100
+ return 0;
101
+ }
102
+
103
+ void spfa_run(int* dist, int* q, bool* in_q, int& head, int& tail,
104
+ const State& s, int seat, int self_id, int move_cost, int wall_cost, bool can_steel, bool add_congestion) {
105
+ int op = 1 - seat;
106
+ while (head != tail) {
107
+ int u = q[head++];
108
+ if (head == CELLS) head = 0;
109
+ in_q[u] = false;
110
+ int du = dist[u];
111
+ for (int dir = 0; dir < 4; ++dir) {
112
+ int v = u + DIR_DELTA[dir];
113
+ uint8_t g = s.grid[v];
114
+ if (g == OUTSIDE || g == WATER || g == BASE0 || g == BASE1) continue;
115
+ if (g == STEEL && !can_steel) continue;
116
+ if (G.is_base_shield[seat][v] && (g == BRICK || g == STEEL)) continue;
117
+ int step_c = (g == EMPTY) ? move_cost : wall_cost;
118
+
119
+ if (add_congestion && v != s.tank[self_id].pos) {
120
+ int occ = tank_at(s, v);
121
+ if (occ >= 0 && occ != self_id) {
122
+ step_c += (seat_of(occ) == seat) ? 5 : 4;
123
+ }
124
+ for (int et = 0; et < 3; ++et) {
125
+ int eid = tank_id(op, et);
126
+ if (s.tank[eid].alive && can_fire(s, eid) && manhattan(v, s.tank[eid].pos) == 1) {
127
+ if (s.tank[self_id].hp <= s.tank[eid].hp) step_c += 3;
128
+ }
129
+ }
130
+ }
131
+
132
+ if (du + step_c < dist[v]) {
133
+ dist[v] = du + step_c;
134
+ if (!in_q[v]) {
135
+ in_q[v] = true;
136
+ q[tail++] = v;
137
+ if (tail == CELLS) tail = 0;
138
+ }
139
+ }
140
+ }
141
+ }
142
+ }
143
+
144
+ void precompute(const State& s) {
145
+ std::memset(&G, 0, sizeof(G));
146
+ std::memcpy(G.root_grid, s.grid, sizeof(s.grid));
147
+ for (int seat = 0; seat < 2; ++seat) {
148
+ for (int c = 0; c < CELLS; ++c) G.base_ray_lane[seat][c] = -1;
149
+ }
150
+
151
+ const int n = s.P.size;
152
+ const int R = s.P.ring;
153
+
154
+ for (int seat = 0; seat < 2; ++seat) {
155
+ int bpos = s.base_pos[seat];
156
+ int bx = cell_x(bpos), by = cell_y(bpos);
157
+ for (int y = std::max(0, by - R); y <= std::min(n - 1, by + R); ++y) {
158
+ for (int x = std::max(0, bx - R); x <= std::min(n - 1, bx + R); ++x) {
159
+ int c = cell_of(x, y);
160
+ if (c != bpos) G.is_base_shield[seat][c] = true;
161
+ }
162
+ }
163
+ for (int d = 0; d < 4; ++d) {
164
+ int to_base_dir = d ^ 1;
165
+ int lane = (d == LEFT) ? 0 : (d == RIGHT ? 2 : 1);
166
+ for (int k = 1;; ++k) {
167
+ int c = bpos + k * DIR_DELTA[d];
168
+ uint8_t g = s.grid[c];
169
+ if (g == OUTSIDE || g == BASE0 || g == BASE1) break;
170
+ if (k <= 3 && (g == BRICK || g == STEEL)) {
171
+ G.is_base_shield[seat][c] = true;
172
+ }
173
+ G.base_ray_dist[seat][c] = k;
174
+ G.base_ray_dir[seat][c] = to_base_dir;
175
+ G.base_ray_lane[seat][c] = lane;
176
+ }
177
+ }
178
+ }
179
+
180
+ int q[CELLS];
181
+ bool in_q[CELLS] = {};
182
+
183
+ // Supply distances
184
+ for (int k = 0; k < s.n_supply; ++k) {
185
+ for (int c = 0; c < CELLS; ++c) G.dist_supply[k][c] = INF;
186
+ int src = s.supply[k];
187
+ int head = 0, tail = 0;
188
+ G.dist_supply[k][src] = 0;
189
+ q[tail++] = src;
190
+ in_q[src] = true;
191
+ while (head != tail) {
192
+ int u = q[head++];
193
+ if (head == CELLS) head = 0;
194
+ in_q[u] = false;
195
+ int du = G.dist_supply[k][u];
196
+ for (int d = 0; d < 4; ++d) {
197
+ int v = u + DIR_DELTA[d];
198
+ uint8_t g = s.grid[v];
199
+ if (G.is_base_shield[0][v] || G.is_base_shield[1][v]) {
200
+ if (g == BRICK || g == STEEL) continue;
201
+ }
202
+ int w = (g == EMPTY) ? 1 : (g == BRICK ? 3 : INF);
203
+ if (w < INF && du + w < G.dist_supply[k][v]) {
204
+ G.dist_supply[k][v] = du + w;
205
+ if (!in_q[v]) {
206
+ in_q[v] = true;
207
+ q[tail++] = v;
208
+ if (tail == CELLS) tail = 0;
209
+ }
210
+ }
211
+ }
212
+ }
213
+ }
214
+
215
+ // Per-tank Dijkstra for the 3 lanes of enemy base and distance from tank
216
+ for (int i = 0; i < 6; ++i) {
217
+ int seat = seat_of(i);
218
+ int op = 1 - seat;
219
+ int spd = s.P.types[type_of(i)].speed;
220
+ int rl = reload_turns(s, i);
221
+ bool can_steel = (s.tank[i].level >= 2 && s.P.level2_steel);
222
+ int move_cost = (spd >= 2) ? 1 : 2;
223
+ int wall_cost = move_cost + 2 + 2 * rl;
224
+
225
+ for (int c = 0; c < CELLS; ++c) {
226
+ G.dist_atk[i][c] = INF;
227
+ G.dist_from_tank[i][c] = INF;
228
+ for (int l = 0; l < 3; ++l) G.dist_lane[i][l][c] = INF;
229
+ }
230
+
231
+ int bpos = s.base_pos[op];
232
+
233
+ for (int d = 0; d < 4; ++d) {
234
+ int check_c = bpos + DIR_DELTA[d];
235
+ if (s.grid[check_c] == OUTSIDE) continue;
236
+ int lane = (d == LEFT) ? 0 : (d == RIGHT ? 2 : 1);
237
+ int head = 0, tail = 0;
238
+
239
+ int bricks_b = 0, steels_b = 0;
240
+ for (int k = 1;; ++k) {
241
+ int c = bpos + k * DIR_DELTA[d];
242
+ uint8_t g = s.grid[c];
243
+ if (g == OUTSIDE || g == BASE0 || g == BASE1) break;
244
+ if (steels_b > 0 && !can_steel) break;
245
+
246
+ if (g != WATER && (g != STEEL || can_steel)) {
247
+ int walls_b = bricks_b + steels_b;
248
+ int shot_interval = 2 + 2 * rl + (k > 2 ? (k - 1) : 0);
249
+ int post_cost = walls_b * shot_interval;
250
+ if (k == 1) post_cost += 0;
251
+ else if (k == 2) post_cost += 1;
252
+ else post_cost += 2 + (k - 2) * 3;
253
+
254
+ for (int t = 0; t < 3; ++t) {
255
+ int sp = s.spawn[tank_id(op, t)];
256
+ if (sp == c) post_cost += 5;
257
+ else if (c == bpos + DIR_DELTA[d] && sp == bpos + 2 * DIR_DELTA[d]) {
258
+ post_cost += 3;
259
+ }
260
+ }
261
+ if (g == BRICK || g == STEEL) {
262
+ post_cost += 2 + 2 * rl;
263
+ }
264
+ if (post_cost < G.dist_lane[i][lane][c]) {
265
+ G.dist_lane[i][lane][c] = post_cost;
266
+ if (!in_q[c]) {
267
+ in_q[c] = true;
268
+ q[tail++] = c;
269
+ if (tail == CELLS) tail = 0;
270
+ }
271
+ }
272
+ }
273
+ if (g == BRICK) bricks_b++;
274
+ else if (g == STEEL) steels_b++;
275
+ }
276
+
277
+ spfa_run(G.dist_lane[i][lane], q, in_q, head, tail, s, seat, i, move_cost, wall_cost, can_steel, true);
278
+ }
279
+
280
+ for (int c = 0; c < CELLS; ++c) {
281
+ G.dist_atk[i][c] = std::min({G.dist_lane[i][0][c], G.dist_lane[i][1][c], G.dist_lane[i][2][c]});
282
+ }
283
+
284
+ int start_c = s.tank[i].alive ? s.tank[i].pos : s.spawn[i];
285
+ int head = 0, tail = 0;
286
+ G.dist_from_tank[i][start_c] = 0;
287
+ q[tail++] = start_c;
288
+ in_q[start_c] = true;
289
+ spfa_run(G.dist_from_tank[i], q, in_q, head, tail, s, seat, i, move_cost, wall_cost, can_steel, false);
290
+ }
291
+
292
+ // Role Assignment for each seat
293
+ int next_turn = s.turn + 1;
294
+ int turns_to_spawn = INF;
295
+ if (next_turn <= s.P.supply_first) {
296
+ turns_to_spawn = s.P.supply_first - next_turn;
297
+ } else {
298
+ int rem = (next_turn - s.P.supply_first) % s.P.supply_period;
299
+ turns_to_spawn = (rem == 0) ? 0 : (s.P.supply_period - rem);
300
+ }
301
+
302
+ for (int seat = 0; seat < 2; ++seat) {
303
+ int op = 1 - seat;
304
+ for (int tp = 0; tp < 3; ++tp) {
305
+ int i = tank_id(seat, tp);
306
+ G.assigned_lane[i] = 1;
307
+ G.assigned_supply[i] = -1;
308
+ G.assigned_def_enemy[i] = -1;
309
+ G.assigned_def_urgency[i] = 0;
310
+ }
311
+
312
+ // 1. Ironclad Defense Assignment
313
+ for (int et = 0; et < 3; ++et) {
314
+ int e = tank_id(op, et);
315
+ int th = enemy_base_threat_level(s, seat, e);
316
+ if (th >= 2) {
317
+ int best_t = -1, second_t = -1;
318
+ int best_d = INF, second_d = INF;
319
+ for (int tp = 0; tp < 3; ++tp) {
320
+ int i = tank_id(seat, tp);
321
+ int pos = s.tank[i].alive ? s.tank[i].pos : s.spawn[i];
322
+ if (s.tank[i].alive && G.dist_atk[i][pos] <= 1 && s.base_hp[op] <= 4 && s.base_hp[seat] >= s.base_hp[op] + 3) {
323
+ continue;
324
+ }
325
+ int d = G.dist_from_tank[e][pos] + (s.tank[i].alive ? 0 : respawn_in(s, i) * 3);
326
+ if (s.tank[i].alive && G.dist_atk[i][pos] <= 2) d += 10;
327
+ if (d < best_d) {
328
+ second_d = best_d; second_t = best_t;
329
+ best_d = d; best_t = i;
330
+ } else if (d < second_d) {
331
+ second_d = d; second_t = i;
332
+ }
333
+ }
334
+ if (best_t >= 0 && (th == 3 || best_d <= 24)) {
335
+ if (th > G.assigned_def_urgency[best_t]) {
336
+ G.assigned_def_enemy[best_t] = e;
337
+ G.assigned_def_urgency[best_t] = th;
338
+ }
339
+ }
340
+ if (th == 3 && (s.tank[e].level >= 1 || s.tank[e].hp >= 2) && second_t >= 0 && second_d <= 14) {
341
+ if (th > G.assigned_def_urgency[second_t]) {
342
+ G.assigned_def_enemy[second_t] = e;
343
+ G.assigned_def_urgency[second_t] = th;
344
+ }
345
+ }
346
+ }
347
+ }
348
+
349
+ // 2. Supply / Star assignment
350
+ bool sup_taken[MAX_SUPPLY] = {};
351
+ for (int step_match = 0; step_match < 3; ++step_match) {
352
+ int best_i = -1, best_k = -1, best_d = INF;
353
+ for (int tp = 0; tp < 3; ++tp) {
354
+ int i = tank_id(seat, tp);
355
+ if (!s.tank[i].alive || s.tank[i].level >= s.P.max_level) continue;
356
+ if (G.assigned_supply[i] >= 0) continue;
357
+ if (G.assigned_def_urgency[i] >= 3) continue;
358
+ if (G.dist_atk[i][s.tank[i].pos] <= 2) continue;
359
+
360
+ for (int k = 0; k < s.n_supply; ++k) {
361
+ if (sup_taken[k]) continue;
362
+ bool has_star = ((s.star_mask >> k) & 1) != 0;
363
+ if (!has_star && turns_to_spawn > 9) continue;
364
+ int ds = G.dist_supply[k][s.tank[i].pos];
365
+ int max_reach = has_star ? 10 : (turns_to_spawn + 3);
366
+ if (ds <= max_reach && ds < best_d) {
367
+ best_d = ds;
368
+ best_i = i;
369
+ best_k = k;
370
+ }
371
+ }
372
+ }
373
+ if (best_i >= 0 && best_k >= 0) {
374
+ G.assigned_supply[best_i] = best_k;
375
+ sup_taken[best_k] = true;
376
+ }
377
+ }
378
+
379
+ // 3. Multi-Lane Attack Assignment
380
+ int best_l[3] = {0, 1, 2};
381
+ int best_total_cost = INF;
382
+ for (int l0 = 0; l0 < 3; ++l0) {
383
+ for (int l1 = 0; l1 < 3; ++l1) {
384
+ for (int l2 = 0; l2 < 3; ++l2) {
385
+ int ls[3] = {l0, l1, l2};
386
+ int cost = 0;
387
+ for (int tp = 0; tp < 3; ++tp) {
388
+ int i = tank_id(seat, tp);
389
+ int pos = s.tank[i].alive ? s.tank[i].pos : s.spawn[i];
390
+ int d = G.dist_lane[i][ls[tp]][pos];
391
+ if (d >= INF / 2) d = 200;
392
+ if (ls[tp] != 1) d -= 2;
393
+ cost += d;
394
+ }
395
+ if (l0 == l1) cost += 16;
396
+ if (l0 == l2) cost += 16;
397
+ if (l1 == l2) cost += 16;
398
+ if (cost < best_total_cost) {
399
+ best_total_cost = cost;
400
+ best_l[0] = l0; best_l[1] = l1; best_l[2] = l2;
401
+ }
402
+ }
403
+ }
404
+ }
405
+ for (int tp = 0; tp < 3; ++tp) {
406
+ G.assigned_lane[tank_id(seat, tp)] = best_l[tp];
407
+ }
408
+
409
+ for (int c = 0; c < CELLS; ++c) {
410
+ if (s.grid[c] != BRICK && s.grid[c] != STEEL) continue;
411
+ if (G.is_base_shield[seat][c]) continue;
412
+ if (G.is_base_shield[op][c] || G.base_ray_dist[op][c] > 0) {
413
+ G.is_atk_wall[seat][c] = true;
414
+ continue;
415
+ }
416
+ for (int tp = 0; tp < 3; ++tp) {
417
+ int i = tank_id(seat, tp);
418
+ if (!s.tank[i].alive) continue;
419
+ int pos = s.tank[i].pos;
420
+ if (manhattan(pos, c) <= 2) {
421
+ int lane = G.assigned_lane[i];
422
+ if (G.dist_lane[i][lane][c] < G.dist_lane[i][lane][pos] ||
423
+ G.dist_atk[i][c] < G.dist_atk[i][pos]) {
424
+ G.is_atk_wall[seat][c] = true;
425
+ }
426
+ int sup = G.assigned_supply[i];
427
+ if (sup >= 0 && G.dist_supply[sup][c] < G.dist_supply[sup][pos]) {
428
+ G.is_atk_wall[seat][c] = true;
429
+ }
430
+ int def_e = G.assigned_def_enemy[i];
431
+ if (def_e >= 0 && G.dist_from_tank[def_e][c] < G.dist_from_tank[def_e][pos]) {
432
+ G.is_atk_wall[seat][c] = true;
433
+ }
434
+ }
435
+ }
436
+ }
437
+ }
438
+ }
439
+
440
+ struct RayHit {
441
+ int kind; // 0=none/outside, 1=brick, 2=steel, 3=my_base, 4=op_base, 5=ally_tank, 6=enemy_tank, 7=enemy_bullet
442
+ int dist;
443
+ int target_id;
444
+ int cell;
445
+ };
446
+
447
+ RayHit trace_shot(const State& s, int seat, int self_id, int pos, int dir) {
448
+ int opp_dir = dir ^ 1;
449
+ for (int step = 1;; ++step) {
450
+ int c = pos + step * DIR_DELTA[dir];
451
+ for (int k = 0; k < s.n_bullets; ++k) {
452
+ if (s.bullet[k].pos == c && s.bullet[k].dir == opp_dir) {
453
+ return RayHit{7, step, k, c};
454
+ }
455
+ }
456
+ uint8_t g = s.grid[c];
457
+ if (g == OUTSIDE) return RayHit{0, step, -1, c};
458
+ if (g == BASE0 + seat) return RayHit{3, step, seat, c};
459
+ if (g == BASE0 + (1 - seat)) return RayHit{4, step, 1 - seat, c};
460
+ if (g == BRICK) return RayHit{1, step, -1, c};
461
+ if (g == STEEL) return RayHit{2, step, -1, c};
462
+ if (g == EMPTY) {
463
+ int tid = tank_at(s, c);
464
+ if (tid >= 0 && tid != self_id) {
465
+ if (seat_of(tid) == seat) return RayHit{5, step, tid, c};
466
+ else return RayHit{6, step, tid, c};
467
+ }
468
+ }
469
+ }
470
+ }
471
+
472
+ struct MoveSafety {
473
+ int p1, p2;
474
+ int existing_bullet_hits;
475
+ int direct_enemy_threat;
476
+ int cross_enemy_threat;
477
+ int cancels_enemy_bullet;
478
+ int cancels_base_bullet;
479
+ int intercepts_enemy_fire;
480
+ };
481
+
482
+ MoveSafety check_cmd_safety(const State& s, int i, Cmd c) {
483
+ MoveSafety ms{};
484
+ const Tank& t = s.tank[i];
485
+ int seat = seat_of(i);
486
+ int op = 1 - seat;
487
+ int pos = t.pos;
488
+ ms.p1 = pos;
489
+ ms.p2 = pos;
490
+
491
+ bool fired = false;
492
+ int fire_dir = -1;
493
+ int f1 = NO_CELL, f2 = NO_CELL;
494
+ if (is_move(c)) {
495
+ int d1 = c - 1;
496
+ int n1 = pos + DIR_DELTA[d1];
497
+ if (s.grid[n1] == EMPTY && tank_at(s, n1) < 0) ms.p1 = n1;
498
+ ms.p2 = ms.p1;
499
+ } else if (is_move2(c)) {
500
+ int d1 = (c - 9) >> 2, d2 = (c - 9) & 3;
501
+ int n1 = pos + DIR_DELTA[d1];
502
+ if (s.grid[n1] == EMPTY && tank_at(s, n1) < 0) ms.p1 = n1;
503
+ int n2 = ms.p1 + DIR_DELTA[d2];
504
+ if (s.grid[n2] == EMPTY && (n2 == pos || tank_at(s, n2) < 0)) ms.p2 = n2;
505
+ } else if (is_fire(c) && can_fire(s, i)) {
506
+ fired = true;
507
+ fire_dir = c - 5;
508
+ f1 = pos + DIR_DELTA[fire_dir];
509
+ if ((s.grid[f1] == EMPTY || s.grid[f1] == WATER) && tank_at(s, f1) < 0) {
510
+ f2 = f1 + DIR_DELTA[fire_dir];
511
+ }
512
+ }
513
+
514
+ // 1. Existing bullets on board
515
+ for (int k = 0; k < s.n_bullets; ++k) {
516
+ const Bullet& b = s.bullet[k];
517
+ int b1 = b.pos + DIR_DELTA[b.dir];
518
+ if (fired) {
519
+ bool cancel1 = (b.dir == (fire_dir ^ 1) && b.pos == f1) || (b1 == f1 && (s.grid[f1] == EMPTY || s.grid[f1] == WATER));
520
+ if (cancel1) {
521
+ if (seat_of(b.owner) != seat) {
522
+ ms.cancels_enemy_bullet++;
523
+ if (G.base_ray_dist[seat][b.pos] > 0 && G.base_ray_dir[seat][b.pos] == b.dir) {
524
+ ms.cancels_base_bullet++;
525
+ }
526
+ }
527
+ continue;
528
+ }
529
+ }
530
+ if (ms.p1 != pos && b1 == pos && ms.p1 == b.pos) {
531
+ ms.existing_bullet_hits++;
532
+ continue;
533
+ }
534
+ if (b1 == ms.p1 && s.grid[b1] == EMPTY) {
535
+ ms.existing_bullet_hits++;
536
+ continue;
537
+ }
538
+ uint8_t g1 = s.grid[b1];
539
+ if (g1 != EMPTY && g1 != WATER) continue;
540
+ int v1 = tank_at(s, b1);
541
+ if (v1 >= 0 && v1 != i) continue;
542
+
543
+ int b2 = b1 + DIR_DELTA[b.dir];
544
+ if (fired && f2 != NO_CELL) {
545
+ bool cancel2 = (b.dir == (fire_dir ^ 1) && b1 == f2) || (b2 == f2 && (s.grid[f2] == EMPTY || s.grid[f2] == WATER));
546
+ if (cancel2) {
547
+ if (seat_of(b.owner) != seat) {
548
+ ms.cancels_enemy_bullet++;
549
+ if (G.base_ray_dist[seat][b.pos] > 0 && G.base_ray_dir[seat][b.pos] == b.dir) {
550
+ ms.cancels_base_bullet++;
551
+ }
552
+ }
553
+ continue;
554
+ }
555
+ }
556
+ if (ms.p2 != ms.p1 && b2 == ms.p1 && ms.p2 == b1) {
557
+ ms.existing_bullet_hits++;
558
+ continue;
559
+ }
560
+ if (b2 == ms.p2 && s.grid[b2] == EMPTY) {
561
+ ms.existing_bullet_hits++;
562
+ continue;
563
+ }
564
+ }
565
+
566
+ // 2. Potential shots from armed enemy tanks (lazy trace_shot only when cancel or hit occurs!)
567
+ for (int t_idx = 0; t_idx < 3; ++t_idx) {
568
+ int e = tank_id(op, t_idx);
569
+ if (!s.tank[e].alive || !can_fire(s, e)) continue;
570
+ int epos = s.tank[e].pos;
571
+ for (int ed = 0; ed < 4; ++ed) {
572
+ int eb1 = epos + DIR_DELTA[ed];
573
+ if (fired) {
574
+ bool cancel1 = (ed == (fire_dir ^ 1) && epos == f1) || (eb1 == f1 && (s.grid[f1] == EMPTY || s.grid[f1] == WATER));
575
+ if (cancel1) {
576
+ RayHit erh = trace_shot(s, op, e, epos, ed);
577
+ if (erh.kind == 6 || erh.kind == 4 || (erh.kind == 1 && erh.dist <= 2)) {
578
+ ms.intercepts_enemy_fire++;
579
+ }
580
+ continue;
581
+ }
582
+ }
583
+ bool hit = false;
584
+ if (eb1 == ms.p1 && s.grid[eb1] == EMPTY) {
585
+ hit = true;
586
+ } else {
587
+ uint8_t g1 = s.grid[eb1];
588
+ int v1 = tank_at(s, eb1);
589
+ if ((g1 == EMPTY || g1 == WATER) && (v1 < 0 || v1 == i)) {
590
+ int eb2 = eb1 + DIR_DELTA[ed];
591
+ bool cancelled2 = false;
592
+ if (fired && f2 != NO_CELL) {
593
+ if ((ed == (fire_dir ^ 1) && eb1 == f2) || (eb2 == f2 && (s.grid[f2] == EMPTY || s.grid[f2] == WATER))) {
594
+ cancelled2 = true;
595
+ RayHit erh = trace_shot(s, op, e, epos, ed);
596
+ if (erh.kind == 6 || erh.kind == 4 || (erh.kind == 1 && erh.dist <= 2)) {
597
+ ms.intercepts_enemy_fire++;
598
+ }
599
+ }
600
+ }
601
+ if (!cancelled2 && eb2 == ms.p2 && s.grid[eb2] == EMPTY) {
602
+ hit = true;
603
+ }
604
+ }
605
+ }
606
+ if (hit) {
607
+ RayHit erh = trace_shot(s, op, e, epos, ed);
608
+ if (erh.kind == 6 || erh.kind == 4 || (erh.kind == 1 && erh.dist <= 2)) {
609
+ ms.direct_enemy_threat++;
610
+ } else {
611
+ ms.cross_enemy_threat++;
612
+ }
613
+ }
614
+ }
615
+ }
616
+
617
+ return ms;
618
+ }
619
+
620
+ double score_single_cmd(const State& s, int i, Cmd c, bool use_global_maps = true) {
621
+ const Tank& t = s.tank[i];
622
+ if (!t.alive) return 0.0;
623
+ int seat = seat_of(i);
624
+ int op = 1 - seat;
625
+ int pos = t.pos;
626
+
627
+ MoveSafety ms = check_cmd_safety(s, i, c);
628
+
629
+ if (is_move2(c)) {
630
+ if (ms.p1 == pos && ms.p2 == pos) return -50000.0;
631
+ if (ms.p2 == pos && ms.p1 != pos) return -400.0;
632
+ }
633
+
634
+ double score = 0.0;
635
+
636
+ // 1. Survival against existing bullets and telefrags
637
+ if (ms.existing_bullet_hits > 0) {
638
+ if (ms.existing_bullet_hits >= t.hp) {
639
+ score -= 6500.0 + 1800.0 * t.level;
640
+ } else {
641
+ score -= 2200.0 * ms.existing_bullet_hits;
642
+ }
643
+ }
644
+ if (ms.direct_enemy_threat > 0) {
645
+ if (ms.direct_enemy_threat >= t.hp) {
646
+ score -= 1350.0 + 500.0 * t.level;
647
+ } else {
648
+ score -= 380.0 * ms.direct_enemy_threat;
649
+ }
650
+ }
651
+ if (ms.cross_enemy_threat > 0) {
652
+ if (t.hp == 1) score -= 180.0 * ms.cross_enemy_threat;
653
+ else score -= 45.0 * ms.cross_enemy_threat;
654
+ }
655
+ if (ms.cancels_enemy_bullet > 0) {
656
+ score += 240.0 * ms.cancels_enemy_bullet;
657
+ }
658
+ if (ms.cancels_base_bullet > 0) {
659
+ score += 1350.0 * ms.cancels_base_bullet;
660
+ }
661
+ if (ms.intercepts_enemy_fire > 0) {
662
+ score += 320.0 * ms.intercepts_enemy_fire;
663
+ }
664
+
665
+ if (is_enemy_spawn_soon(s, seat, ms.p2)) {
666
+ score -= 9000.0;
667
+ }
668
+ if (is_ally_spawn_soon(s, seat, i, ms.p2)) {
669
+ score -= 1200.0;
670
+ }
671
+
672
+ bool is_stuck = (last_pos_hist[i][0] == pos && last_pos_hist[i][1] == pos && last_pos_hist[i][2] == pos);
673
+
674
+ // 2. Firing evaluation
675
+ if (is_fire(c)) {
676
+ if (!can_fire(s, i)) return -1e9;
677
+ int d = c - 5;
678
+ RayHit rh = trace_shot(s, seat, i, pos, d);
679
+
680
+ if (rh.kind == 3) {
681
+ return -100000.0;
682
+ }
683
+ if (rh.kind == 5) {
684
+ return -25000.0;
685
+ }
686
+ if (rh.kind == 1) {
687
+ if (G.is_base_shield[seat][rh.cell]) {
688
+ return -25000.0;
689
+ } else if (G.is_base_shield[op][rh.cell] || (G.base_ray_dist[op][pos] > 0 && G.base_ray_dir[op][pos] == d)) {
690
+ if (rh.dist <= 2) score += 620.0;
691
+ else if (rh.dist <= 4) score += 350.0;
692
+ else score += 80.0;
693
+ } else if (rh.dist <= 2) {
694
+ int lane = G.assigned_lane[i];
695
+ if (!use_global_maps || G.is_atk_wall[seat][rh.cell] ||
696
+ G.dist_lane[i][lane][rh.cell] < G.dist_lane[i][lane][pos] ||
697
+ G.dist_atk[i][rh.cell] < G.dist_atk[i][pos]) {
698
+ score += 360.0;
699
+ } else {
700
+ score += 50.0;
701
+ }
702
+ } else {
703
+ score -= (ms.intercepts_enemy_fire > 0 ? 0.0 : 160.0);
704
+ }
705
+ } else if (rh.kind == 2) {
706
+ if (t.level >= 2 && s.P.level2_steel) {
707
+ if (G.is_base_shield[seat][rh.cell]) {
708
+ return -25000.0;
709
+ }
710
+ int lane = G.assigned_lane[i];
711
+ if (rh.dist <= 3 && (!use_global_maps || G.is_atk_wall[seat][rh.cell] ||
712
+ G.dist_lane[i][lane][rh.cell] < G.dist_lane[i][lane][pos] ||
713
+ G.dist_atk[i][rh.cell] < G.dist_atk[i][pos] || G.base_ray_dist[op][pos] > 0)) {
714
+ score += 420.0;
715
+ } else {
716
+ score -= (ms.intercepts_enemy_fire > 0 ? 0.0 : 100.0);
717
+ }
718
+ } else {
719
+ score -= (ms.intercepts_enemy_fire > 0 ? 0.0 : 250.0);
720
+ }
721
+ } else if (rh.kind == 4) {
722
+ int next_c = pos + DIR_DELTA[d];
723
+ bool can_walk_closer = (s.grid[next_c] == EMPTY && tank_at(s, next_c) < 0);
724
+ if (rh.dist <= 2) {
725
+ score += 1300.0;
726
+ } else if (rh.dist <= 4) {
727
+ score += can_walk_closer ? 620.0 : 980.0;
728
+ } else {
729
+ score += can_walk_closer ? 60.0 : 620.0;
730
+ }
731
+ } else if (rh.kind == 6) {
732
+ int enemy_id = rh.target_id;
733
+ const Tank& et = s.tank[enemy_id];
734
+ bool enemy_can_fire = can_fire(s, enemy_id);
735
+ int next_c = pos + DIR_DELTA[d];
736
+ bool can_advance = (s.grid[next_c] == EMPTY && tank_at(s, next_c) < 0);
737
+ int ethreat = enemy_base_threat_level(s, seat, enemy_id);
738
+ double priority_bonus = ethreat * 320.0 + et.level * 240.0 + (type_of(enemy_id) == FIRE ? 120.0 : 0.0);
739
+ if (G.base_ray_dist[op][pos] > 0 && G.base_ray_dir[op][pos] == d) {
740
+ priority_bonus += 450.0;
741
+ }
742
+
743
+ if (rh.dist == 1) {
744
+ score += (enemy_can_fire ? 750.0 : 1200.0) + priority_bonus;
745
+ if (et.hp == 1) score += 450.0;
746
+ } else if (rh.dist == 2) {
747
+ score += (enemy_can_fire ? 600.0 : 1000.0) + priority_bonus;
748
+ if (et.hp == 1) score += 350.0;
749
+ } else if (rh.dist == 3) {
750
+ if (is_stuck && can_advance) score -= 150.0;
751
+ else score += (can_advance ? 30.0 : 180.0) + priority_bonus * 0.25;
752
+ } else {
753
+ if (can_advance) score -= 180.0;
754
+ else if (bullet_cap(s, i) > 1) score += 60.0;
755
+ else score -= 100.0;
756
+ }
757
+ } else if (rh.kind == 7) {
758
+ if (rh.dist <= 3) score += 260.0;
759
+ else score -= 60.0;
760
+ } else {
761
+ score -= (ms.intercepts_enemy_fire > 0 ? 0.0 : 240.0);
762
+ }
763
+ }
764
+
765
+ // 3. Role-based positional evaluation of `ms.p2`
766
+ if (use_global_maps) {
767
+ int def_e = G.assigned_def_enemy[i];
768
+ int def_urg = G.assigned_def_urgency[i];
769
+ int sup_k = G.assigned_supply[i];
770
+ int lane = G.assigned_lane[i];
771
+
772
+ // A. Defense role
773
+ if (def_e >= 0 && s.tank[def_e].alive) {
774
+ int de_now = G.dist_from_tank[def_e][pos];
775
+ int de_next = G.dist_from_tank[def_e][ms.p2];
776
+ double w = (def_urg == 3) ? 125.0 : 75.0;
777
+ if (de_next < INF) {
778
+ score += (de_now - de_next) * w - de_next * 4.0;
779
+ }
780
+ }
781
+
782
+ // B. Star collection role
783
+ if (sup_k >= 0 && t.level < s.P.max_level) {
784
+ bool has_star = ((s.star_mask >> sup_k) & 1) != 0;
785
+ int sup_cell = s.supply[sup_k];
786
+ int ds_now = G.dist_supply[sup_k][pos];
787
+ int ds_next = G.dist_supply[sup_k][ms.p2];
788
+ if (has_star && ms.p2 == sup_cell) {
789
+ score += 1050.0;
790
+ } else {
791
+ double w = has_star ? 68.0 : 48.0;
792
+ score += (ds_now - ds_next) * w - ds_next * 4.0;
793
+ if (ms.p2 == sup_cell) score += 180.0;
794
+ }
795
+ }
796
+
797
+ // C. Assigned Attack Lane progress
798
+ int dl_now = G.dist_lane[i][lane][pos];
799
+ int dl_next = G.dist_lane[i][lane][ms.p2];
800
+ int da_now = G.dist_atk[i][pos];
801
+ int da_next = G.dist_atk[i][ms.p2];
802
+
803
+ double lane_w = (def_e >= 0 && def_urg == 3) ? 0.0 : (def_e >= 0 ? 12.0 : (sup_k >= 0 ? 22.0 : 42.0));
804
+ double atk_w = (def_e >= 0 && def_urg == 3) ? 0.0 : (def_e >= 0 ? 8.0 : (sup_k >= 0 ? 12.0 : 20.0));
805
+
806
+ if (dl_next < INF) {
807
+ score += (dl_now - dl_next) * lane_w - dl_next * (lane_w > 0 ? 1.5 : 0.0);
808
+ }
809
+ if (da_next < INF) {
810
+ score += (da_now - da_next) * atk_w - da_next * (atk_w > 0 ? 1.0 : 0.0);
811
+ }
812
+
813
+ if (is_stuck && ms.p2 == pos && !is_fire(c)) {
814
+ score -= 120.0;
815
+ }
816
+ for (int h = 0; h < 4; ++h) {
817
+ if (ms.p2 == last_pos_hist[i][h] && ms.p2 != pos) {
818
+ score -= 18.0;
819
+ }
820
+ }
821
+ }
822
+
823
+ return score;
824
+ }
825
+
826
+ Orders fast_rollout_orders(const State& s, int seat) {
827
+ Orders ord;
828
+ int chosen_p1[3] = {-1, -1, -1};
829
+ int chosen_p2[3] = {-1, -1, -1};
830
+ bool base_shot_taken = false;
831
+ int op = 1 - seat;
832
+
833
+ for (int type = 0; type < 3; ++type) {
834
+ int i = tank_id(seat, type);
835
+ if (!s.tank[i].alive) {
836
+ ord.cmd[type] = STAY;
837
+ continue;
838
+ }
839
+ Cmd list[N_CMDS];
840
+ int n = legal_commands(s, i, list);
841
+ double best_sc = -1e18;
842
+ Cmd best_c = STAY;
843
+
844
+ for (int k = 0; k < n; ++k) {
845
+ Cmd c = list[k];
846
+ double sc = score_single_cmd(s, i, c, true);
847
+ MoveSafety ms = check_cmd_safety(s, i, c);
848
+
849
+ for (int prev = 0; prev < type; ++prev) {
850
+ if (ms.p1 == chosen_p1[prev] || ms.p2 == chosen_p2[prev]) {
851
+ sc -= 600.0;
852
+ }
853
+ }
854
+ if (is_fire(c) && base_shot_taken) {
855
+ int d = c - 5;
856
+ int pos = s.tank[i].pos;
857
+ if (G.base_ray_dist[op][pos] > 0 && G.base_ray_dir[op][pos] == d) {
858
+ int b_cnt = 0, s_cnt = 0;
859
+ count_ray_obstacles(s, op, pos, b_cnt, s_cnt);
860
+ if (b_cnt == 0 && s_cnt == 0) sc -= 950.0;
861
+ }
862
+ }
863
+
864
+ if (sc > best_sc) {
865
+ best_sc = sc;
866
+ best_c = c;
867
+ }
868
+ }
869
+ ord.cmd[type] = best_c;
870
+ MoveSafety ms = check_cmd_safety(s, i, best_c);
871
+ chosen_p1[type] = ms.p1;
872
+ chosen_p2[type] = ms.p2;
873
+ if (is_fire(best_c)) {
874
+ int d = best_c - 5;
875
+ int pos = s.tank[i].pos;
876
+ if (G.base_ray_dist[op][pos] > 0 && G.base_ray_dir[op][pos] == d && G.base_ray_dist[op][pos] <= 2) {
877
+ int b_cnt = 0, s_cnt = 0;
878
+ count_ray_obstacles(s, op, pos, b_cnt, s_cnt);
879
+ if (b_cnt == 0 && s_cnt == 0) base_shot_taken = true;
880
+ }
881
+ }
882
+ }
883
+ return ord;
884
+ }
885
+
886
+ double evaluate_state(const State& s, int me) {
887
+ int op = 1 - me;
888
+ if (s.done) {
889
+ if (s.winner == me) {
890
+ return 1000000.0 + (300 - s.turn) * 500.0 + (s.base_hp[me] - s.base_hp[op]) * 2000.0 + (s.kills[me] - s.kills[op]) * 200.0;
891
+ } else if (s.winner == op) {
892
+ return -1000000.0 - (300 - s.turn) * 500.0 + (s.base_hp[me] - s.base_hp[op]) * 2000.0 + (s.kills[me] - s.kills[op]) * 200.0;
893
+ } else {
894
+ return 0.0;
895
+ }
896
+ }
897
+
898
+ double val = 0.0;
899
+
900
+ // 1. Base HP
901
+ int my_bhp = s.base_hp[me];
902
+ int op_bhp = s.base_hp[op];
903
+ val += (my_bhp - op_bhp) * 3800.0;
904
+ if (my_bhp <= 6) val -= (7 - my_bhp) * 1400.0;
905
+ if (op_bhp <= 6) val += (7 - op_bhp) * 1400.0;
906
+
907
+ // 2. Kills and Stars Eaten
908
+ val += (s.kills[me] - s.kills[op]) * 950.0;
909
+ val += (s.stars_eaten[me] - s.stars_eaten[op]) * 850.0;
910
+
911
+ // 3. Base shield walls & attack walls destroyed since root
912
+ for (int seat = 0; seat < 2; ++seat) {
913
+ double sgn = (seat == me) ? 1.0 : -1.0;
914
+ int bpos = s.base_pos[seat];
915
+ for (int d = 0; d < 4; ++d) {
916
+ int c1 = bpos + DIR_DELTA[d];
917
+ if (s.grid[c1] == BRICK) val += sgn * 360.0;
918
+ }
919
+ }
920
+ if (s.wall_hash != 0) {
921
+ for (int c = 0; c < CELLS; ++c) {
922
+ if (G.root_grid[c] != EMPTY && s.grid[c] == EMPTY) {
923
+ if (G.is_base_shield[me][c]) val -= 500.0;
924
+ else if (G.is_base_shield[op][c]) val += 420.0;
925
+ else {
926
+ if (G.is_atk_wall[me][c]) val += 260.0;
927
+ if (G.is_atk_wall[op][c]) val -= 220.0;
928
+ }
929
+ }
930
+ }
931
+ }
932
+
933
+ // 4. Bullets currently flying toward either base
934
+ for (int k = 0; k < s.n_bullets; ++k) {
935
+ const Bullet& b = s.bullet[k];
936
+ for (int seat = 0; seat < 2; ++seat) {
937
+ if (G.base_ray_dist[seat][b.pos] > 0 && G.base_ray_dir[seat][b.pos] == b.dir) {
938
+ int dist = G.base_ray_dist[seat][b.pos];
939
+ int bricks_in_way = 0, steels_in_way = 0;
940
+ count_ray_obstacles(s, seat, b.pos, bricks_in_way, steels_in_way);
941
+ if (steels_in_way == 0 || b.steel) {
942
+ double sgn = (seat == me) ? -1.0 : 1.0;
943
+ if (bricks_in_way == 0 && dist <= 6) {
944
+ val += sgn * (1650.0 - dist * 180.0);
945
+ } else if (bricks_in_way == 1 && dist <= 4) {
946
+ val += sgn * 340.0;
947
+ }
948
+ }
949
+ }
950
+ }
951
+ }
952
+
953
+ // 5. Tank alive status, HP, level, reload/ammo readiness, and bullet danger
954
+ int best_supply_dist[2][MAX_SUPPLY];
955
+ for (int k = 0; k < s.n_supply; ++k) {
956
+ best_supply_dist[0][k] = INF;
957
+ best_supply_dist[1][k] = INF;
958
+ }
959
+
960
+ int best_atk_dist[2] = {INF, INF};
961
+ int second_atk_dist[2] = {INF, INF};
962
+ int lanes_occupied[2][3] = {};
963
+
964
+ for (int i = 0; i < 6; ++i) {
965
+ int seat = seat_of(i);
966
+ int op_seat = 1 - seat;
967
+ double sgn = (seat == me) ? 1.0 : -1.0;
968
+ const Tank& t = s.tank[i];
969
+
970
+ if (!t.alive) {
971
+ int wait = respawn_in(s, i);
972
+ int sp_dist = G.dist_atk[i][s.spawn[i]];
973
+ if (sp_dist >= INF / 2) sp_dist = 30;
974
+ val -= sgn * (900.0 + wait * 260.0 + sp_dist * 16.0);
975
+ continue;
976
+ }
977
+
978
+ double tank_val = 1150.0 + t.hp * 580.0;
979
+ if (t.level == 1) tank_val += 1350.0;
980
+ else if (t.level >= 2) tank_val += 2700.0;
981
+
982
+ if (can_fire(s, i)) {
983
+ tank_val += 110.0;
984
+ } else {
985
+ for (int k = 0; k < s.n_bullets; ++k) {
986
+ if (s.bullet[k].owner == i) {
987
+ int bp = s.bullet[k].pos;
988
+ int bd = s.bullet[k].dir;
989
+ int steps_left = 0;
990
+ for (int st = 1; st <= 12; ++st) {
991
+ int nc = bp + st * DIR_DELTA[bd];
992
+ uint8_t g = s.grid[nc];
993
+ if (g != EMPTY && g != WATER) break;
994
+ if (tank_at(s, nc) >= 0) break;
995
+ steps_left++;
996
+ }
997
+ if (steps_left >= 3 && bullet_cap(s, i) == 1) {
998
+ tank_val -= steps_left * 35.0;
999
+ }
1000
+ }
1001
+ }
1002
+ }
1003
+
1004
+ int survival_options = 0;
1005
+ Cmd list[N_CMDS];
1006
+ int nc = legal_commands(s, i, list);
1007
+ for (int k = 0; k < nc; ++k) {
1008
+ MoveSafety ms = check_cmd_safety(s, i, list[k]);
1009
+ if (ms.existing_bullet_hits == 0 && !is_enemy_spawn_soon(s, seat, ms.p2)) {
1010
+ survival_options++;
1011
+ }
1012
+ }
1013
+ if (survival_options == 0) {
1014
+ if (t.hp <= 1) tank_val -= (1150.0 + t.hp * 580.0 + t.level * 1350.0 + 750.0);
1015
+ else tank_val -= 650.0;
1016
+ } else if (survival_options <= 2) {
1017
+ tank_val -= 120.0;
1018
+ }
1019
+
1020
+ int th = enemy_base_threat_level(s, op_seat, i);
1021
+ if (th == 3) {
1022
+ tank_val += 550.0 + t.hp * 200.0 + (can_fire(s, i) ? 200.0 : 0.0);
1023
+ } else if (th == 2) {
1024
+ tank_val += 240.0 + t.hp * 100.0;
1025
+ }
1026
+
1027
+ val += sgn * tank_val;
1028
+
1029
+ int d_atk = G.dist_atk[i][t.pos];
1030
+ if (G.base_ray_dist[op_seat][t.pos] > 0) {
1031
+ int rdist = G.base_ray_dist[op_seat][t.pos];
1032
+ int rbricks = 0, rsteels = 0;
1033
+ count_ray_obstacles(s, op_seat, t.pos, rbricks, rsteels);
1034
+ bool can_st = (t.level >= 2 && s.P.level2_steel);
1035
+ if (rsteels == 0 || can_st) {
1036
+ int cur_walls = rbricks + rsteels;
1037
+ int dynamic_d = cur_walls * 4 + (rdist <= 2 ? 0 : (rdist - 2) * 2);
1038
+ if (dynamic_d < d_atk) d_atk = dynamic_d;
1039
+ int rlane = G.base_ray_lane[op_seat][t.pos];
1040
+ if (rlane >= 0 && rdist <= 6 && cur_walls <= 1) {
1041
+ lanes_occupied[seat][rlane]++;
1042
+ }
1043
+ }
1044
+ }
1045
+ if (d_atk < best_atk_dist[seat]) {
1046
+ second_atk_dist[seat] = best_atk_dist[seat];
1047
+ best_atk_dist[seat] = d_atk;
1048
+ } else if (d_atk < second_atk_dist[seat]) {
1049
+ second_atk_dist[seat] = d_atk;
1050
+ }
1051
+
1052
+ int def_e = G.assigned_def_enemy[i];
1053
+ int def_urg = G.assigned_def_urgency[i];
1054
+ if (def_e >= 0 && def_urg >= 2 && s.tank[def_e].alive) {
1055
+ int dd = G.dist_from_tank[def_e][t.pos];
1056
+ if (dd < INF / 2) {
1057
+ val += sgn * (-dd * (def_urg == 3 ? 45.0 : 25.0));
1058
+ }
1059
+ } else {
1060
+ int lane = G.assigned_lane[i];
1061
+ int dl = G.dist_lane[i][lane][t.pos];
1062
+ if (dl < INF / 2) {
1063
+ val += sgn * (-dl * 14.0);
1064
+ } else if (d_atk < INF / 2) {
1065
+ val += sgn * (-d_atk * 14.0);
1066
+ }
1067
+ }
1068
+
1069
+ if (t.level < s.P.max_level) {
1070
+ for (int k = 0; k < s.n_supply; ++k) {
1071
+ int ds = G.dist_supply[k][t.pos];
1072
+ if (ds < best_supply_dist[seat][k]) {
1073
+ best_supply_dist[seat][k] = ds;
1074
+ }
1075
+ }
1076
+ }
1077
+ }
1078
+
1079
+ // 6. Base Attack Progress & Multi-Angle Surround Bonus
1080
+ for (int seat = 0; seat < 2; ++seat) {
1081
+ double sgn = (seat == me) ? 1.0 : -1.0;
1082
+ if (best_atk_dist[seat] < INF / 2) {
1083
+ val += sgn * (1200.0 - best_atk_dist[seat] * 40.0);
1084
+ }
1085
+ if (second_atk_dist[seat] < INF / 2) {
1086
+ val += sgn * (650.0 - second_atk_dist[seat] * 24.0);
1087
+ }
1088
+ int active_lanes = (lanes_occupied[seat][0] > 0) + (lanes_occupied[seat][1] > 0) + (lanes_occupied[seat][2] > 0);
1089
+ if (active_lanes >= 2) {
1090
+ val += sgn * 380.0 * (active_lanes - 1);
1091
+ }
1092
+ }
1093
+
1094
+ // 7. Star Control evaluation
1095
+ int turns_to_spawn = INF;
1096
+ if (s.turn < s.P.supply_first) {
1097
+ turns_to_spawn = s.P.supply_first - s.turn;
1098
+ } else {
1099
+ int rem = (s.turn - s.P.supply_first) % s.P.supply_period;
1100
+ turns_to_spawn = (rem == 0) ? s.P.supply_period : (s.P.supply_period - rem);
1101
+ }
1102
+
1103
+ for (int k = 0; k < s.n_supply; ++k) {
1104
+ bool has_star = ((s.star_mask >> k) & 1) != 0;
1105
+ if (has_star || turns_to_spawn <= 8) {
1106
+ double weight = has_star ? 55.0 : (9 - turns_to_spawn) * 5.5;
1107
+ if (best_supply_dist[me][k] < INF) {
1108
+ val += std::max(0.0, (13.0 - best_supply_dist[me][k]) * weight);
1109
+ }
1110
+ if (best_supply_dist[op][k] < INF) {
1111
+ val -= std::max(0.0, (13.0 - best_supply_dist[op][k]) * weight);
1112
+ }
1113
+ }
1114
+ }
1115
+
1116
+ // 8. Defensive Interception & Anti-Crowding
1117
+ for (int seat = 0; seat < 2; ++seat) {
1118
+ double sgn = (seat == me) ? 1.0 : -1.0;
1119
+ int enemy = 1 - seat;
1120
+ for (int et = 0; et < 3; ++et) {
1121
+ int eid = tank_id(enemy, et);
1122
+ if (!s.tank[eid].alive) continue;
1123
+ int th = enemy_base_threat_level(s, seat, eid);
1124
+ if (th >= 2) {
1125
+ int min_def_dist = INF;
1126
+ for (int mt = 0; mt < 3; ++mt) {
1127
+ int mid = tank_id(seat, mt);
1128
+ if (s.tank[mid].alive) {
1129
+ int d = G.dist_from_tank[eid][s.tank[mid].pos];
1130
+ if (d < min_def_dist) min_def_dist = d;
1131
+ }
1132
+ }
1133
+ if (min_def_dist < INF / 2) {
1134
+ double urgency = (th == 3) ? 75.0 : 40.0;
1135
+ val -= sgn * min_def_dist * urgency;
1136
+ }
1137
+ }
1138
+ }
1139
+
1140
+ for (int a = 0; a < 3; ++a) {
1141
+ int ia = tank_id(seat, a);
1142
+ if (!s.tank[ia].alive) continue;
1143
+ for (int b = a + 1; b < 3; ++b) {
1144
+ int ib = tank_id(seat, b);
1145
+ if (!s.tank[ib].alive) continue;
1146
+ int md = manhattan(s.tank[ia].pos, s.tank[ib].pos);
1147
+ if (md == 1) val -= sgn * 45.0;
1148
+ else if (md == 2 && (cell_x(s.tank[ia].pos) == cell_x(s.tank[ib].pos) ||
1149
+ cell_y(s.tank[ia].pos) == cell_y(s.tank[ib].pos))) {
1150
+ val -= sgn * 25.0;
1151
+ }
1152
+ }
1153
+ }
1154
+ }
1155
+
1156
+ return val;
1157
+ }
1158
+
1159
+ struct ScoredCmd {
1160
+ Cmd c;
1161
+ double sc;
1162
+ MoveSafety ms;
1163
+ };
1164
+
1165
+ struct ScoredOrders {
1166
+ Orders ord;
1167
+ double prior;
1168
+ };
1169
+
1170
+ int generate_candidate_orders(const State& s, int seat, ScoredOrders* out, int max_out) {
1171
+ ScoredCmd top_cmds[3][8];
1172
+ int n_top[3] = {0, 0, 0};
1173
+
1174
+ for (int type = 0; type < 3; ++type) {
1175
+ int i = tank_id(seat, type);
1176
+ if (!s.tank[i].alive) {
1177
+ top_cmds[type][0] = ScoredCmd{STAY, 0.0, MoveSafety{}};
1178
+ n_top[type] = 1;
1179
+ continue;
1180
+ }
1181
+ Cmd list[N_CMDS];
1182
+ int n = legal_commands(s, i, list);
1183
+ ScoredCmd all[N_CMDS];
1184
+ int valid_n = 0;
1185
+ for (int k = 0; k < n; ++k) {
1186
+ double sc = score_single_cmd(s, i, list[k], true);
1187
+ if (sc <= -40000.0) continue;
1188
+ all[valid_n].c = list[k];
1189
+ all[valid_n].sc = sc;
1190
+ all[valid_n].ms = check_cmd_safety(s, i, list[k]);
1191
+ valid_n++;
1192
+ }
1193
+ if (valid_n == 0) {
1194
+ top_cmds[type][0] = ScoredCmd{STAY, 0.0, check_cmd_safety(s, i, STAY)};
1195
+ n_top[type] = 1;
1196
+ continue;
1197
+ }
1198
+ std::sort(all, all + valid_n, [](const ScoredCmd& a, const ScoredCmd& b) {
1199
+ return a.sc > b.sc;
1200
+ });
1201
+
1202
+ int keep = std::min(valid_n, (type == FAST ? 5 : 4));
1203
+ for (int k = 0; k < keep; ++k) {
1204
+ top_cmds[type][k] = all[k];
1205
+ }
1206
+ n_top[type] = keep;
1207
+ }
1208
+
1209
+ ScoredOrders pool[120];
1210
+ int n_pool = 0;
1211
+ int op = 1 - seat;
1212
+
1213
+ for (int a = 0; a < n_top[0]; ++a) {
1214
+ for (int b = 0; b < n_top[1]; ++b) {
1215
+ for (int c = 0; c < n_top[2]; ++c) {
1216
+ const ScoredCmd* scs[3] = {&top_cmds[0][a], &top_cmds[1][b], &top_cmds[2][c]};
1217
+ double total = scs[0]->sc + scs[1]->sc + scs[2]->sc;
1218
+
1219
+ for (int u = 0; u < 3; ++u) {
1220
+ int iu = tank_id(seat, u);
1221
+ if (!s.tank[iu].alive) continue;
1222
+ for (int v = u + 1; v < 3; ++v) {
1223
+ int iv = tank_id(seat, v);
1224
+ if (!s.tank[iv].alive) continue;
1225
+ if (scs[u]->ms.p1 == scs[v]->ms.p1 || scs[u]->ms.p2 == scs[v]->ms.p2) {
1226
+ total -= 750.0;
1227
+ }
1228
+ if (scs[u]->ms.p1 == s.tank[iv].pos && scs[v]->ms.p1 == s.tank[iu].pos) {
1229
+ total -= 750.0;
1230
+ }
1231
+ if (is_fire(scs[u]->c)) {
1232
+ int fd = scs[u]->c - 5;
1233
+ int p = s.tank[iu].pos;
1234
+ if (p + DIR_DELTA[fd] == scs[v]->ms.p1 || p + 2 * DIR_DELTA[fd] == scs[v]->ms.p2) {
1235
+ total -= 5000.0;
1236
+ }
1237
+ }
1238
+ if (is_fire(scs[v]->c)) {
1239
+ int fd = scs[v]->c - 5;
1240
+ int p = s.tank[iv].pos;
1241
+ if (p + DIR_DELTA[fd] == scs[u]->ms.p1 || p + 2 * DIR_DELTA[fd] == scs[u]->ms.p2) {
1242
+ total -= 5000.0;
1243
+ }
1244
+ }
1245
+ }
1246
+ }
1247
+
1248
+ int base_shots = 0;
1249
+ for (int u = 0; u < 3; ++u) {
1250
+ int iu = tank_id(seat, u);
1251
+ if (!s.tank[iu].alive || !is_fire(scs[u]->c)) continue;
1252
+ int d = scs[u]->c - 5;
1253
+ int pos = s.tank[iu].pos;
1254
+ if (G.base_ray_dist[op][pos] > 0 && G.base_ray_dir[op][pos] == d && G.base_ray_dist[op][pos] <= 2) {
1255
+ int b_cnt = 0, s_cnt = 0;
1256
+ count_ray_obstacles(s, op, pos, b_cnt, s_cnt);
1257
+ if (b_cnt == 0 && s_cnt == 0) base_shots++;
1258
+ }
1259
+ }
1260
+ if (base_shots >= 2) total -= 850.0 * (base_shots - 1);
1261
+
1262
+ Orders ord;
1263
+ ord.cmd[0] = scs[0]->c;
1264
+ ord.cmd[1] = scs[1]->c;
1265
+ ord.cmd[2] = scs[2]->c;
1266
+ pool[n_pool++] = ScoredOrders{ord, total};
1267
+ }
1268
+ }
1269
+ }
1270
+
1271
+ std::sort(pool, pool + n_pool, [](const ScoredOrders& x, const ScoredOrders& y) {
1272
+ return x.prior > y.prior;
1273
+ });
1274
+
1275
+ int res = std::min(n_pool, max_out);
1276
+ for (int k = 0; k < res; ++k) out[k] = pool[k];
1277
+ return res;
1278
+ }
1279
+
1280
+ } // namespace
1281
+
1282
+ void init(const State& s, int me) {
1283
+ (void)me;
1284
+ std::memset(last_pos_hist, -1, sizeof(last_pos_hist));
1285
+ hist_turn = -1;
1286
+ precompute(s);
1287
+ }
1288
+
1289
+ Orders decide(const State& s, int me) {
1290
+ int op = 1 - me;
1291
+ if (s.turn != hist_turn) {
1292
+ for (int i = 0; i < 6; ++i) {
1293
+ for (int h = 5; h > 0; --h) last_pos_hist[i][h] = last_pos_hist[i][h - 1];
1294
+ last_pos_hist[i][0] = s.tank[i].alive ? s.tank[i].pos : -1;
1295
+ }
1296
+ hist_turn = s.turn;
1297
+ }
1298
+
1299
+ precompute(s);
1300
+
1301
+ constexpr int MAX_MY = 32;
1302
+ constexpr int MAX_OP = 22;
1303
+ ScoredOrders my_cands[MAX_MY];
1304
+ ScoredOrders op_cands[MAX_OP];
1305
+
1306
+ int n_my = generate_candidate_orders(s, me, my_cands, MAX_MY);
1307
+ int n_op = generate_candidate_orders(s, op, op_cands, MAX_OP);
1308
+
1309
+ if (n_my <= 0) return Orders{};
1310
+ if (n_my == 1) return my_cands[0].ord;
1311
+
1312
+ double op_weights[MAX_OP];
1313
+ double max_op_p = op_cands[0].prior;
1314
+ double sum_op_w = 0.0;
1315
+ for (int j = 0; j < n_op; ++j) {
1316
+ op_weights[j] = std::exp(std::max(-10.0, (op_cands[j].prior - max_op_p) / 420.0));
1317
+ sum_op_w += op_weights[j];
1318
+ }
1319
+ for (int j = 0; j < n_op; ++j) op_weights[j] /= sum_op_w;
1320
+
1321
+ static double mat[MAX_MY][MAX_OP];
1322
+ static State s1_cache[MAX_MY][MAX_OP];
1323
+
1324
+ for (int i = 0; i < n_my; ++i) {
1325
+ for (int j = 0; j < n_op; ++j) {
1326
+ State& s1 = s1_cache[i][j];
1327
+ s1 = s;
1328
+ step_as(s1, me, my_cands[i].ord, op_cands[j].ord);
1329
+ mat[i][j] = evaluate_state(s1, me) + my_cands[i].prior * 0.06;
1330
+ }
1331
+ }
1332
+
1333
+ for (int i = 0; i < n_my; ++i) {
1334
+ if (budget_left() < 6.0) break;
1335
+ for (int j = 0; j < std::min(n_op, 14); ++j) {
1336
+ if (budget_left() < 5.0) break;
1337
+ const State& s1 = s1_cache[i][j];
1338
+ if (s1.done) continue;
1339
+ State s2 = s1;
1340
+ Orders r0 = fast_rollout_orders(s2, 0);
1341
+ Orders r1 = fast_rollout_orders(s2, 1);
1342
+ step(s2, r0, r1);
1343
+ double v2 = evaluate_state(s2, me);
1344
+
1345
+ if (!s2.done && j < 8 && budget_left() > 10.0) {
1346
+ State s3 = s2;
1347
+ Orders r30 = fast_rollout_orders(s3, 0);
1348
+ Orders r31 = fast_rollout_orders(s3, 1);
1349
+ step(s3, r30, r31);
1350
+ double v3 = evaluate_state(s3, me);
1351
+ mat[i][j] = 0.40 * mat[i][j] + 0.35 * v2 + 0.25 * v3;
1352
+ } else {
1353
+ mat[i][j] = 0.50 * mat[i][j] + 0.50 * v2;
1354
+ }
1355
+ }
1356
+ }
1357
+
1358
+ int best_idx = 0;
1359
+ double best_total = -1e18;
1360
+ int top_op_k = std::min(n_op, 8);
1361
+
1362
+ for (int i = 0; i < n_my; ++i) {
1363
+ double worst_top = 1e18;
1364
+ double expected = 0.0;
1365
+
1366
+ for (int j = 0; j < n_op; ++j) {
1367
+ double v = mat[i][j];
1368
+ if (j < top_op_k && v < worst_top) worst_top = v;
1369
+ expected += op_weights[j] * v;
1370
+ }
1371
+
1372
+ double softmin_num = 0.0;
1373
+ double softmin_den = 0.0;
1374
+ for (int j = 0; j < n_op; ++j) {
1375
+ double v = mat[i][j];
1376
+ double w = op_weights[j] * std::exp(std::min(12.0, (worst_top - v) / 600.0));
1377
+ softmin_num += w * v;
1378
+ softmin_den += w;
1379
+ }
1380
+ double softmin = (softmin_den > 0.0) ? (softmin_num / softmin_den) : worst_top;
1381
+
1382
+ double combined = 0.45 * expected + 0.35 * softmin + 0.20 * worst_top;
1383
+ if (combined > best_total) {
1384
+ best_total = combined;
1385
+ best_idx = i;
1386
+ }
1387
+ }
1388
+
1389
+ return my_cands[best_idx].ord;
1390
+ }