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,726 @@
1
+ // 夺旗 bot
2
+ // 核心认识:
3
+ // 1) 攻击"免费":所有攻击同时结算,阵亡者的攻击照样生效,所以出手本身不会招来额外伤害;
4
+ // 真正决定受伤的是"站位"。=> 只要够得着就打,但不要用走位去送。
5
+ // 2) 集火:3 人同打一个 = 102 伤害 = 秒杀;2 人 = 68,两回合必杀。局部以多打少是唯一稳定赚的交换。
6
+ // 3) 携旗者不能攻击 => 猎杀携旗者零风险(阻止 1 分 + 旗子落地可抢)。
7
+ // 4) 34 伤害 ≈ 0.24 分,1 步路程 ≈ 0.04 分 => 值得绕几步路避开一次挨打。
8
+ // 用"危险加权 Dijkstra"规划路线,绕开敌方火力覆盖区;血量低时权重急剧升高(逃跑)。
9
+ // 5) 旗子随机刷新在旗点,"去拿旗"这一段取决于我在哪,"送回家"这一段与位置无关
10
+ // => 空闲时停在到各旗点距离和最小处(中心附近)。
11
+ // 6) 移动与动作相互独立:一边赶路一边出手。
12
+ #include <bits/stdc++.h>
13
+ using namespace std;
14
+
15
+ struct Unit { int id, team, x, y, hp, flag, respawn_at; };
16
+ struct Flag { int id; string status; int x, y, holder, return_at; };
17
+ struct Event { string type; vector<int> args; };
18
+
19
+ struct Game {
20
+ int teams = 0, me = 0, size = 0, turns = 0, hp = 0, damage = 0, respawn = 0, flag_return = 0, flag_cooldown = 0;
21
+ vector<string> map;
22
+ vector<pair<int, int>> bases;
23
+ vector<pair<int, int>> spots;
24
+ int turn = 0;
25
+ vector<int> score;
26
+ vector<Unit> units;
27
+ vector<Flag> flags;
28
+ vector<Event> events;
29
+ };
30
+
31
+ bool read_turn(Game& g) {
32
+ string w;
33
+ while (cin >> w) {
34
+ if (w == "INIT") {
35
+ cin >> g.teams >> g.me >> g.size >> g.turns >> g.hp >> g.damage >> g.respawn >> g.flag_return >> g.flag_cooldown;
36
+ } else if (w == "MAP") {
37
+ g.map.assign(g.size, "");
38
+ for (auto& row : g.map) cin >> row;
39
+ } else if (w == "BASES") {
40
+ int k; cin >> k; g.bases.resize(k);
41
+ for (auto& b : g.bases) cin >> b.first >> b.second;
42
+ } else if (w == "SPOTS") {
43
+ int k; cin >> k; g.spots.resize(k);
44
+ for (auto& s : g.spots) cin >> s.first >> s.second;
45
+ } else if (w == "TURN") {
46
+ cin >> g.turn;
47
+ } else if (w == "SCORE") {
48
+ g.score.resize(g.teams);
49
+ for (auto& s : g.score) cin >> s;
50
+ } else if (w == "UNITS") {
51
+ int k; cin >> k; g.units.resize(k);
52
+ for (auto& u : g.units) { string tag; cin >> tag >> u.id >> u.team >> u.x >> u.y >> u.hp >> u.flag >> u.respawn_at; }
53
+ } else if (w == "FLAGS") {
54
+ int k; cin >> k; g.flags.resize(k);
55
+ for (auto& f : g.flags) { string tag; cin >> tag >> f.id >> f.status >> f.x >> f.y >> f.holder >> f.return_at; }
56
+ } else if (w == "EVENTS") {
57
+ int k; cin >> k; g.events.assign(k, {});
58
+ string line; getline(cin, line);
59
+ for (auto& e : g.events) {
60
+ getline(cin, line);
61
+ istringstream in(line);
62
+ string tag; in >> tag >> e.type;
63
+ for (int v; in >> v;) e.args.push_back(v);
64
+ }
65
+ } else if (w == "END") {
66
+ return true;
67
+ }
68
+ }
69
+ return false;
70
+ }
71
+
72
+ // ================================ 可调参数 ================================
73
+ static double W_BASE = 0.12; // 每点预期伤害折算成多少"步"(多方混战需要绕开火力网)
74
+ static double W_HURT2 = 0.16; // 只剩 2 下就死
75
+ static double W_HURT1 = 0.85; // 只剩 1 下就死
76
+ static double W_CARRY = 1.45; // 携旗时更怕死
77
+ static double W_SUP = 0.55; // 附近有队友支援时更敢打
78
+ static double LETHAL = 6.0; // "这一格会让我当场被打死"的额外代价(步),大队数时用这个
79
+ static double DANGCAP = 0.0; // 单格危险代价上限(步),大队数时用这个
80
+ static double LETHALS = 60.0; // 小队伍(<=5 方)额外加上的必死格代价
81
+ static double DANGCAPS = 3.0; // 小队伍额外加上的单格危险上限
82
+ // 人少时火力点稀疏、绕得开,值得按伤害绕路;人多时满地都是危险,绕路只会既丢旗又丢击杀。
83
+ // 用 (8-n)/6 在两者之间线性过渡:n=2 完全按小队伍处理,n>=8 完全按大队数处理。
84
+ static double DANGMID = 8.0; // 过渡中点
85
+ static double KILLVAL = 3.0; // 秒杀一个敌人的收益
86
+ static double CARRBON = 1.6; // 打携旗者的额外收益
87
+ static double DMGVAL = 0.024; // 每点伤害收益
88
+ static double HUNTSC = 0.85; // 猎杀价值系数
89
+ static double HUNTMIN = 0.010; // 猎杀最低阈值
90
+ static double FOCUSP = 1.7; // 集火凸度:伤害越接近击杀线,价值增长越快
91
+ static double PICKSAFE = 0.0; // 实测:犹豫等清场丢掉的旗远多于被伏击的损失,所以默认立刻拿
92
+ static double PICKRAD = 4.0; // 判断"附近有没有敌人"的半径
93
+ static double HUNTNT = 1.0; // 猎杀价值随阵营数衰减的强度(多方时抢旗远比追杀划算)
94
+ static double HUNTDIST = 20.0; // 2 方时愿意花多少回合去拦截;多方按 2/n 衰减
95
+ // 实测 20 优于 12:2 方 23.6->25.1,8 方 rank 5.12->4.38
96
+ static double RUNMIN = 0.0; // 抢旗收益门槛。必须为 0:多方时"总有敌人更近",设门槛会导致完全不去抢旗
97
+ static double SUPFIRE = 1.0; // 是否为了集火而主动靠拢已经打起来的敌人
98
+ static double POSBIAS = 0.0; // 占位时向自家一侧偏移的权重(多方时中心是绞肉机)
99
+ static double POSDANG = 0.10; // 占位时避开火力覆盖的权重
100
+ static double THOMEW = 1.0; // 回程距离的权重(实测 2 反而更差)
101
+ static double ESCORTN = 0.0; // 专职护卫实测更差:少一个抢旗的人,收益抵不上保护效果
102
+
103
+ // ================================ AI ================================
104
+ const int INF = 1000000000;
105
+
106
+ struct AI {
107
+ Game* g = nullptr;
108
+ int SZ = 0, NT = 0, NU = 0, NF = 0, DMG = 34, ME = 0, ARANGE = 2, NC = 0;
109
+ vector<unsigned char> wall;
110
+ int ctr = 0;
111
+ vector<vector<int>> baseCells, spawnOrder, dBase, dSpot;
112
+ vector<int> spotCells, dCtr, spotSum;
113
+ vector<vector<int>> dF, dU;
114
+ vector<int> dCar, dmgAt, pos, pred, enemyAt, enemyWant, flagAt, myNear;
115
+ vector<unsigned char> imm;
116
+ vector<int> bq;
117
+ int carrierCell = -1;
118
+ double dangCap = 0.0, lethalW = 6.0;
119
+ bool inited = false;
120
+
121
+ inline int md(int a, int b) const { return abs(a % SZ - b % SZ) + abs(a / SZ - b / SZ); }
122
+
123
+ void bfs(const vector<int>& src, vector<int>& dist) {
124
+ dist.assign((size_t)NC, INF);
125
+ bq.clear();
126
+ for (int s : src) if (s >= 0 && !wall[s] && dist[s] == INF) { dist[s] = 0; bq.push_back(s); }
127
+ for (size_t h = 0; h < bq.size(); h++) {
128
+ int c = bq[h], x = c % SZ, y = c / SZ, d = dist[c] + 1;
129
+ if (x > 0) { int n = c - 1; if (!wall[n] && dist[n] == INF) { dist[n] = d; bq.push_back(n); } }
130
+ if (x + 1 < SZ) { int n = c + 1; if (!wall[n] && dist[n] == INF) { dist[n] = d; bq.push_back(n); } }
131
+ if (y > 0) { int n = c - SZ; if (!wall[n] && dist[n] == INF) { dist[n] = d; bq.push_back(n); } }
132
+ if (y + 1 < SZ) { int n = c + SZ; if (!wall[n] && dist[n] == INF) { dist[n] = d; bq.push_back(n); } }
133
+ }
134
+ }
135
+
136
+ // 危险加权最短路:返回每个格子到目标的最小代价(×100 取整)
137
+ void dijk(const vector<int>& src, double w, double lethalAt, vector<int>& cost) {
138
+ cost.assign((size_t)NC, INF);
139
+ priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
140
+ for (int s : src) if (s >= 0 && !wall[s] && cost[s] == INF) { cost[s] = 0; pq.push({0, s}); }
141
+ while (!pq.empty()) {
142
+ auto [d, c] = pq.top(); pq.pop();
143
+ if (d != cost[c]) continue;
144
+ int x = c % SZ, y = c / SZ;
145
+ const int dx[4] = {0, 0, -1, 1}, dy[4] = {-1, 1, 0, 0};
146
+ for (int i = 0; i < 4; i++) {
147
+ int nx = x + dx[i], ny = y + dy[i];
148
+ if (nx < 0 || ny < 0 || nx >= SZ || ny >= SZ) continue;
149
+ int n = ny * SZ + nx;
150
+ if (wall[n]) continue;
151
+ double step = 1.0 + min(dmgAt[n] * w, dangCap);
152
+ if (lethalAt > 0 && dmgAt[n] >= lethalAt) step += lethalW;
153
+ int nd = d + (int)(step * 100.0 + 0.5);
154
+ if (nd < cost[n]) { cost[n] = nd; pq.push({nd, n}); }
155
+ }
156
+ }
157
+ }
158
+
159
+ void initStatic() {
160
+ SZ = g->size; NT = g->teams; ME = g->me; DMG = g->damage; ARANGE = 2; NC = SZ * SZ;
161
+ double fSc = max(0.0, min(1.0, (DANGMID - NT) / 6.0));
162
+ dangCap = DANGCAP + DANGCAPS * fSc;
163
+ lethalW = LETHAL + LETHALS * fSc;
164
+ wall.assign((size_t)NC, 0);
165
+ for (int y = 0; y < SZ; y++) for (int x = 0; x < SZ; x++) wall[(size_t)y * SZ + x] = (g->map[y][x] == '#');
166
+ int c = SZ / 2; ctr = c * SZ + c;
167
+
168
+ baseCells.assign(NT, {});
169
+ spawnOrder.assign(NT, {});
170
+ for (int t = 0; t < NT; t++) {
171
+ int bx = g->bases[t].first, by = g->bases[t].second;
172
+ vector<int> cells;
173
+ for (int dy = -1; dy <= 1; dy++) for (int dx = -1; dx <= 1; dx++) {
174
+ int x = bx + dx, y = by + dy;
175
+ if (x < 0 || y < 0 || x >= SZ || y >= SZ) continue;
176
+ if (wall[(size_t)y * SZ + x]) continue;
177
+ cells.push_back(y * SZ + x);
178
+ }
179
+ baseCells[t] = cells;
180
+ vector<int> so = cells;
181
+ sort(so.begin(), so.end(), [&](int a, int b) {
182
+ int da = md(a, ctr), db = md(b, ctr);
183
+ if (da != db) return da < db;
184
+ int ka = abs(a % SZ - bx) + 2 * abs(a / SZ - by);
185
+ int kb = abs(b % SZ - bx) + 2 * abs(b / SZ - by);
186
+ if (ka != kb) return ka < kb;
187
+ return a < b;
188
+ });
189
+ spawnOrder[t] = so;
190
+ }
191
+ spotCells.clear();
192
+ for (auto& s : g->spots) spotCells.push_back(s.second * SZ + s.first);
193
+
194
+ dmgAt.assign((size_t)NC, 0);
195
+ dBase.assign(NT, {});
196
+ for (int t = 0; t < NT; t++) bfs(baseCells[t], dBase[t]);
197
+ bfs({ctr}, dCtr);
198
+ dSpot.assign(spotCells.size(), {});
199
+ for (size_t i = 0; i < spotCells.size(); i++) bfs({spotCells[i]}, dSpot[i]);
200
+ spotSum.assign((size_t)NC, 0);
201
+ for (int c2 = 0; c2 < NC; c2++) {
202
+ if (wall[c2]) { spotSum[c2] = INF; continue; }
203
+ long long s = 0;
204
+ for (size_t i = 0; i < dSpot.size(); i++) { int d = dSpot[i][c2]; s += (d >= INF ? 60 : d); }
205
+ spotSum[c2] = (int)s;
206
+ }
207
+ inited = true;
208
+ }
209
+
210
+ void computeDmgAt() {
211
+ fill(dmgAt.begin(), dmgAt.end(), 0);
212
+ for (auto& e : g->units) {
213
+ if (e.team == ME || pos[e.id] < 0 || imm[e.id]) continue;
214
+ int ex = pos[e.id] % SZ, ey = pos[e.id] / SZ;
215
+ for (int dy = -3; dy <= 3; dy++) for (int dx = -3; dx <= 3; dx++) {
216
+ int ad = abs(dx) + abs(dy);
217
+ if (ad > 3 || ad == 0) continue;
218
+ int nx = ex + dx, ny = ey + dy;
219
+ if (nx < 0 || ny < 0 || nx >= SZ || ny >= SZ) continue;
220
+ int c = ny * SZ + nx;
221
+ if (wall[c]) continue;
222
+ dmgAt[c] += (ad <= 2 ? DMG : (DMG * 45 / 100));
223
+ }
224
+ }
225
+ }
226
+
227
+ void buildTurn() {
228
+ NU = (int)g->units.size(); NF = (int)g->flags.size();
229
+ pos.assign(NU, -1);
230
+ imm.assign(NU, 0);
231
+ vector<unsigned char> occ((size_t)NC, 0);
232
+ for (auto& u : g->units) if (u.x >= 0) { pos[u.id] = u.y * SZ + u.x; occ[pos[u.id]] = 1; }
233
+ for (auto& u : g->units) {
234
+ if (u.x < 0 && u.respawn_at >= 0 && u.respawn_at <= g->turn) {
235
+ for (int c : spawnOrder[u.team]) if (!occ[c]) { pos[u.id] = c; occ[c] = 1; imm[u.id] = 1; break; }
236
+ }
237
+ }
238
+ dF.assign(NF, {});
239
+ for (auto& f : g->flags) if (f.x >= 0) bfs({f.y * SZ + f.x}, dF[f.id]);
240
+ dU.assign(NU, {});
241
+ for (int k = 0; k < 3; k++) { int id = 3 * ME + k; if (id < NU && pos[id] >= 0) bfs({pos[id]}, dU[id]); }
242
+ flagAt.assign((size_t)NC, -1);
243
+ for (auto& f : g->flags) if (f.x >= 0) { int c = f.y * SZ + f.x; if (flagAt[c] < 0 || f.id < flagAt[c]) flagAt[c] = f.id; }
244
+ carrierCell = -1;
245
+ for (int k = 0; k < 3; k++) {
246
+ int id = 3 * ME + k;
247
+ if (id < NU && pos[id] >= 0 && g->units[id].flag >= 0) { carrierCell = pos[id]; break; }
248
+ }
249
+ if (carrierCell >= 0) bfs({carrierCell}, dCar); else dCar.clear();
250
+ computeDmgAt();
251
+
252
+ enemyAt.assign((size_t)NC, -1);
253
+ enemyWant.assign((size_t)NC, -1);
254
+ pred.assign(NU, -1);
255
+ for (auto& u : g->units) {
256
+ if (u.team == ME || pos[u.id] < 0) continue;
257
+ enemyAt[pos[u.id]] = u.id;
258
+ int p = predict(u.id);
259
+ pred[u.id] = p;
260
+ if (p >= 0) enemyWant[p] = u.id;
261
+ }
262
+ }
263
+
264
+ int predict(int e) {
265
+ int c = pos[e];
266
+ if (c < 0) return -1;
267
+ const vector<int>* dm = nullptr;
268
+ const Unit& u = g->units[e];
269
+ if (u.flag >= 0 && !dBase[u.team].empty() && dBase[u.team][c] < INF) dm = &dBase[u.team];
270
+ else {
271
+ int bf = -1, bd = INF;
272
+ for (auto& f : g->flags) if (f.x >= 0 && !dF[f.id].empty()) { int d = dF[f.id][c]; if (d < bd) { bd = d; bf = f.id; } }
273
+ if (bf >= 0 && bd <= 40) dm = &dF[bf];
274
+ else if (!dCar.empty() && dCar[c] < INF && dCar[c] <= 14) dm = &dCar;
275
+ else dm = &dCtr;
276
+ }
277
+ int best = c, bv = (*dm)[c];
278
+ int x = c % SZ, y = c / SZ;
279
+ const int dx[4] = {0, 0, -1, 1}, dy[4] = {-1, 1, 0, 0};
280
+ for (int i = 0; i < 4; i++) {
281
+ int nx = x + dx[i], ny = y + dy[i];
282
+ if (nx < 0 || ny < 0 || nx >= SZ || ny >= SZ) continue;
283
+ int n = ny * SZ + nx;
284
+ if (wall[n] || (*dm)[n] >= INF) continue;
285
+ if ((*dm)[n] < bv) { bv = (*dm)[n]; best = n; }
286
+ }
287
+ return best;
288
+ }
289
+
290
+ bool canAttack(int a, int b) const {
291
+ if (a < 0 || b < 0) return false;
292
+ int ax = a % SZ, ay = a / SZ, bx = b % SZ, by = b / SZ;
293
+ int dx = bx - ax, dy = by - ay;
294
+ int d = abs(dx) + abs(dy);
295
+ if (d == 0 || d > ARANGE) return false;
296
+ if (abs(dx) == 2 && dy == 0) return !wall[a + dx / 2];
297
+ if (abs(dy) == 2 && dx == 0) return !wall[a + (dy / 2) * SZ];
298
+ if (abs(dx) == 1 && abs(dy) == 1) return !wall[a + dx] || !wall[a + dy * SZ];
299
+ return true;
300
+ }
301
+
302
+ enum { R_CARRY = 0, R_RUN = 1, R_HUNT = 2, R_POS = 3, R_DEAD = 4, R_ESCORT = 5 };
303
+ struct Plan {
304
+ int id = -1, role = R_DEAD, flagId = -1, target = -1;
305
+ vector<int> cost; // ×100 的代价场
306
+ bool useSpot = false;
307
+ };
308
+
309
+ vector<int> buildPath(int from, const vector<int>& dm) {
310
+ vector<int> path;
311
+ if (from < 0 || dm.empty() || dm[from] >= INF) return path;
312
+ int c = from; path.push_back(c);
313
+ for (int guard = 0; guard < 300; guard++) {
314
+ int d = dm[c];
315
+ if (d <= 0) break;
316
+ int x = c % SZ, y = c / SZ, best = -1, bd = d;
317
+ const int dx[4] = {0, 0, -1, 1}, dy[4] = {-1, 1, 0, 0};
318
+ for (int i = 0; i < 4; i++) {
319
+ int nx = x + dx[i], ny = y + dy[i];
320
+ if (nx < 0 || ny < 0 || nx >= SZ || ny >= SZ) continue;
321
+ int n = ny * SZ + nx;
322
+ if (wall[n]) continue;
323
+ if (dm[n] < bd) { bd = dm[n]; best = n; }
324
+ }
325
+ if (best < 0) break;
326
+ c = best; path.push_back(c);
327
+ }
328
+ return path;
329
+ }
330
+
331
+ bool interceptCell(int u, int e, int& out, int& cost) {
332
+ out = -1; cost = INF;
333
+ if (dU[u].empty()) return false;
334
+ const Unit& t = g->units[e];
335
+ vector<int> path;
336
+ if (t.flag >= 0) path = buildPath(pos[e], dBase[t.team]);
337
+ else if (!dCar.empty()) path = buildPath(pos[e], dCar);
338
+ if (path.empty()) { if (pos[e] >= 0) path = {pos[e]}; else return false; }
339
+ int bestArr = INF, bestCell = -1;
340
+ for (int i = 0; i < (int)path.size(); i++) {
341
+ int d = dU[u][path[i]];
342
+ if (d >= INF) continue;
343
+ int arr = max(d, i);
344
+ if (arr < bestArr) { bestArr = arr; bestCell = path[i]; }
345
+ if (d <= i) break;
346
+ }
347
+ if (bestCell < 0) return false;
348
+ out = bestCell; cost = bestArr;
349
+ return true;
350
+ }
351
+
352
+ double runValue(int u, int f, int& marginOut, int& closerOut) {
353
+ const Flag& fl = g->flags[f];
354
+ int fc = fl.y * SZ + fl.x;
355
+ int tgo = dF[f].empty() ? INF : dF[f][pos[u]];
356
+ int thome = dBase[ME][fc];
357
+ if (tgo >= INF) return -1;
358
+ int bestE = INF, closer = 0;
359
+ for (auto& e : g->units) {
360
+ if (e.team == ME || pos[e.id] < 0) continue;
361
+ int d = dF[f][pos[e.id]];
362
+ if (d < bestE) bestE = d;
363
+ if (d < tgo) closer++;
364
+ }
365
+ marginOut = (bestE >= INF ? 99 : bestE - tgo);
366
+ closerOut = closer;
367
+ int m = marginOut;
368
+ double P;
369
+ if (m >= 3) P = 0.95;
370
+ else if (m == 2) P = 0.88;
371
+ else if (m == 1) P = 0.78;
372
+ else if (m == 0) P = 0.60;
373
+ else if (m == -1) P = 0.42;
374
+ else if (m == -2) P = 0.28;
375
+ else P = 0.14;
376
+ if (closer >= 2) P *= 0.75;
377
+ if (closer >= 4) P *= 0.7;
378
+ double val = P / (tgo + THOMEW * thome + 8.0);
379
+ if (tgo == 0) val += 0.6;
380
+ return val;
381
+ }
382
+
383
+ double dangerW(int id) {
384
+ int hp = g->units[id].hp;
385
+ double w = W_BASE;
386
+ if (hp <= DMG) w = W_HURT1;
387
+ else if (hp <= DMG * 2) w = W_HURT2;
388
+ if (g->units[id].flag >= 0) w *= W_CARRY;
389
+ // 队友支援
390
+ int sup = 0;
391
+ for (int k = 0; k < 3; k++) {
392
+ int j = 3 * ME + k;
393
+ if (j == id || j >= NU || pos[j] < 0 || pos[id] < 0) continue;
394
+ if (md(pos[j], pos[id]) <= 5) sup++;
395
+ }
396
+ if (sup >= 1) w *= W_SUP;
397
+ if (sup >= 2) w *= W_SUP;
398
+ return w;
399
+ }
400
+
401
+ bool inMyBase(int c) const { for (int b : baseCells[ME]) if (b == c) return true; return false; }
402
+
403
+ // 地上的旗能等 15 回合。带着旗被贴身就是死路(携旗者不能攻击、也跑不掉),
404
+ // 所以附近还有能打的敌人时先不拿,等清场/旗子快回位/已经快到家再拿。
405
+ bool safePickup(int id, int fin) {
406
+ if (PICKSAFE < 0.5) return true;
407
+ int fid = flagAt[fin];
408
+ if (fid < 0) return false;
409
+ const Flag& fl = g->flags[fid];
410
+ if (fl.return_at >= 0 && fl.return_at - g->turn <= 2) return true;
411
+ if (dBase[ME][fin] <= 3) return true;
412
+ int rad = (int)PICKRAD;
413
+ int threat = 0;
414
+ for (auto& e : g->units) {
415
+ if (e.team == ME || pos[e.id] < 0 || e.flag >= 0) continue; // 敌方携旗者打不了人
416
+ if (md(pos[e.id], fin) <= rad) threat++;
417
+ }
418
+ if (threat == 0) return true;
419
+ int sup = 0;
420
+ for (int k = 0; k < 3; k++) {
421
+ int j = 3 * ME + k;
422
+ if (j == id || j >= NU || pos[j] < 0) continue;
423
+ if (md(pos[j], fin) <= rad + 1) sup++;
424
+ }
425
+ return sup >= threat;
426
+ }
427
+
428
+ string decide() {
429
+ if (!inited) initStatic();
430
+ buildTurn();
431
+
432
+ int ids[3] = {3 * ME, 3 * ME + 1, 3 * ME + 2};
433
+ Plan plans[3];
434
+ vector<int> active;
435
+ vector<int> gf;
436
+ for (auto& f : g->flags) if (f.x >= 0) gf.push_back(f.id);
437
+
438
+ for (int k = 0; k < 3; k++) {
439
+ int id = ids[k];
440
+ plans[k].id = id;
441
+ if (id >= NU || pos[id] < 0) { plans[k].role = R_DEAD; continue; }
442
+ active.push_back(k);
443
+ plans[k].role = R_POS;
444
+ if (g->units[id].flag >= 0) plans[k].role = R_CARRY;
445
+ }
446
+
447
+ // ---- 抢旗分配 ----
448
+ struct Opt { double v; int k, f, margin; };
449
+ vector<Opt> opts;
450
+ double bestRunV[3] = {0, 0, 0};
451
+ for (int k : active) {
452
+ if (plans[k].role == R_CARRY) continue;
453
+ for (int f : gf) {
454
+ int mg, cl;
455
+ double v = runValue(ids[k], f, mg, cl);
456
+ if (v > 0) { opts.push_back({v, k, f, mg}); if (v > bestRunV[k]) bestRunV[k] = v; }
457
+ }
458
+ }
459
+ sort(opts.begin(), opts.end(), [](const Opt& a, const Opt& b) { return a.v > b.v; });
460
+ vector<int> flagTaken(NF, 0);
461
+ int nFree = 0;
462
+ for (int k : active) if (plans[k].role != R_CARRY) nFree++;
463
+ for (auto& o : opts) {
464
+ if (plans[o.k].role != R_POS) continue;
465
+ if (o.v < RUNMIN) break; // 收益太低,宁可占位等下一次刷新
466
+ int cap = 1;
467
+ if ((int)gf.size() < nFree && o.margin <= 1) cap = 2;
468
+ if (flagTaken[o.f] >= cap) continue;
469
+ plans[o.k].role = R_RUN;
470
+ plans[o.k].flagId = o.f;
471
+ plans[o.k].target = g->flags[o.f].y * SZ + g->flags[o.f].x;
472
+ flagTaken[o.f]++;
473
+ }
474
+
475
+ // ---- 其余:猎杀 / 占位 ----
476
+ // 多方混战时,阻止某一家得 1 分只相当于自己得 1 分的 1/(n-1)(两两胜负拆解),
477
+ // 所以阵营越多越应该专心抢旗、少追杀。
478
+ double ntScale = pow(2.0 / max(2, NT), HUNTNT);
479
+ double hmax = HUNTDIST * ntScale;
480
+ for (int k : active) {
481
+ if (plans[k].role != R_POS) continue;
482
+ int id = ids[k];
483
+ int bestE = -1, bestCell = -1, bestC = INF;
484
+ // 敌方携旗者:阻止 1 分 + 旗子落地可抢
485
+ for (auto& e : g->units) {
486
+ if (e.team == ME || pos[e.id] < 0 || e.flag < 0) continue;
487
+ int cell, c2;
488
+ if (!interceptCell(id, e.id, cell, c2)) continue;
489
+ if (c2 > hmax) continue;
490
+ if (c2 < bestC) { bestC = c2; bestE = e.id; bestCell = cell; }
491
+ }
492
+ // 威胁我方携旗者的敌人
493
+ if (carrierCell >= 0 && !dCar.empty()) {
494
+ for (auto& e : g->units) {
495
+ if (e.team == ME || pos[e.id] < 0 || dCar[pos[e.id]] > 7) continue;
496
+ int cell, c2;
497
+ if (!interceptCell(id, e.id, cell, c2)) continue;
498
+ if (c2 > hmax) continue;
499
+ if (c2 < bestC) { bestC = c2; bestE = e.id; bestCell = cell; }
500
+ }
501
+ }
502
+ // 已经贴住队友的敌人 => 靠拢集火(只在真能打出击杀时)
503
+ if (SUPFIRE > 0.5) {
504
+ for (int j = 0; j < 3; j++) {
505
+ int mate = 3 * ME + j;
506
+ if (mate >= NU || pos[mate] < 0) continue;
507
+ for (auto& e : g->units) {
508
+ if (e.team == ME || pos[e.id] < 0 || imm[e.id]) continue;
509
+ if (md(pos[e.id], pos[mate]) > 2) continue;
510
+ if (dU[id].empty() || dU[id][pos[e.id]] > 3) continue;
511
+ if (e.hp > DMG * 2) continue;
512
+ int cell, c2;
513
+ if (!interceptCell(id, e.id, cell, c2)) continue;
514
+ if (c2 > hmax) continue;
515
+ if (c2 < bestC) { bestC = c2; bestE = e.id; bestCell = cell; }
516
+ }
517
+ }
518
+ }
519
+ if (bestE >= 0) {
520
+ plans[k].role = R_HUNT;
521
+ plans[k].target = bestCell;
522
+ }
523
+ }
524
+
525
+ // ---- 护卫:多方时长途运旗几乎必被截杀,派人贴身挡开拦截者 ----
526
+ // 1 个护卫 => 护卫/携旗者与拦截者同归于尽;2 个护卫 => 拦截者 2 回合被秒杀而携旗者能活下来。
527
+ if (carrierCell >= 0 && ESCORTN > 0) {
528
+ int nCarriers = 0;
529
+ for (int k : active) if (plans[k].role == R_CARRY) nCarriers++;
530
+ int nEsc = min(2, (int)(ESCORTN * NT));
531
+ nEsc = min(nEsc, (int)active.size() - nCarriers);
532
+ if (nEsc > 0) {
533
+ vector<int> cp = buildPath(carrierCell, dBase[ME]);
534
+ int ec = cp.size() > 2 ? cp[2] : carrierCell;
535
+ vector<int> cand;
536
+ for (int k : active) if (plans[k].role != R_CARRY) cand.push_back(k);
537
+ sort(cand.begin(), cand.end(), [&](int a, int b) {
538
+ int pa = (plans[a].role == R_RUN) ? 1 : 0, pb = (plans[b].role == R_RUN) ? 1 : 0;
539
+ if (pa != pb) return pa < pb;
540
+ return bestRunV[a] < bestRunV[b];
541
+ });
542
+ for (int i = 0; i < nEsc && i < (int)cand.size(); i++) {
543
+ plans[cand[i]].role = R_ESCORT;
544
+ plans[cand[i]].target = ec;
545
+ }
546
+ }
547
+ }
548
+
549
+ // ---- 为每个单位构建代价场 ----
550
+ for (int k : active) {
551
+ int id = ids[k];
552
+ double w = dangerW(id);
553
+ double leth = (double)g->units[id].hp;
554
+ if (plans[k].role == R_CARRY) dijk(baseCells[ME], w, leth, plans[k].cost);
555
+ else if (plans[k].role == R_RUN) dijk({plans[k].target}, w, leth, plans[k].cost);
556
+ else if (plans[k].role == R_HUNT) dijk({plans[k].target}, w * 0.6, leth, plans[k].cost);
557
+ else if (plans[k].role == R_ESCORT) dijk({plans[k].target}, w * 0.4, leth, plans[k].cost);
558
+ else plans[k].useSpot = true;
559
+ }
560
+
561
+ // ---- 联合移动枚举 ----
562
+ int na = (int)active.size();
563
+ int total = 1; for (int i = 0; i < na; i++) total *= 5;
564
+ const int DX[5] = {0, 0, 0, -1, 1};
565
+ const int DY[5] = {0, -1, 1, 0, 0};
566
+ double bestTotal = -1e18;
567
+ int bestFin[3] = {-1, -1, -1};
568
+ vector<int> bestTgt(3, -1);
569
+
570
+ for (int combo = 0; combo < total; combo++) {
571
+ int mv[3], tmp = combo;
572
+ for (int i = 0; i < na; i++) { mv[i] = tmp % 5; tmp /= 5; }
573
+ int start[3], want[3], fin[3];
574
+ unsigned char blk[3] = {0, 0, 0};
575
+ for (int i = 0; i < na; i++) {
576
+ int id = ids[active[i]];
577
+ start[i] = pos[id];
578
+ int nx = start[i] % SZ + DX[mv[i]], ny = start[i] / SZ + DY[mv[i]];
579
+ int w2 = start[i];
580
+ if (nx >= 0 && ny >= 0 && nx < SZ && ny < SZ) { int c = ny * SZ + nx; if (!wall[c]) w2 = c; }
581
+ want[i] = w2;
582
+ }
583
+ for (int i = 0; i < na; i++) for (int j = i + 1; j < na; j++) if (want[i] == want[j]) { blk[i] = blk[j] = 1; }
584
+ for (int i = 0; i < na; i++) {
585
+ int w2 = want[i];
586
+ if (w2 == start[i]) continue;
587
+ int ea = enemyAt[w2];
588
+ if (ea >= 0 && enemyWant[ea] == w2) { blk[i] = 1; continue; }
589
+ if (ea >= 0 && enemyWant[ea] == start[i]) { blk[i] = 1; continue; }
590
+ if (ea < 0 && enemyWant[w2] >= 0) { blk[i] = 1; continue; }
591
+ for (int j = 0; j < na; j++) if (j != i && start[j] == w2 && (blk[j] || want[j] == start[j])) { blk[i] = 1; break; }
592
+ }
593
+ for (int i = 0; i < na; i++) fin[i] = blk[i] ? start[i] : want[i];
594
+
595
+ double sc = 0;
596
+ for (int i = 0; i < na; i++) {
597
+ int k = active[i], id = ids[k];
598
+ const Plan& p = plans[k];
599
+ if (p.useSpot) {
600
+ sc -= spotSum[fin[i]] / (double)max(1, (int)spotCells.size());
601
+ sc -= POSBIAS * dBase[ME][fin[i]];
602
+ sc -= min(dmgAt[fin[i]] * POSDANG, 3.0);
603
+ } else {
604
+ double d = p.cost.empty() ? 12000 : p.cost[fin[i]];
605
+ if (d >= INF) d = 40000;
606
+ sc -= d / 100.0;
607
+ if (p.role == R_CARRY && inMyBase(fin[i])) sc += 60;
608
+ if (p.role == R_RUN && fin[i] == p.target) sc += safePickup(id, fin[i]) ? 40 : 24;
609
+ if (p.role == R_ESCORT && carrierCell >= 0 && md(fin[i], carrierCell) <= 2) sc += 4;
610
+ }
611
+ }
612
+ vector<int> tgt(3, -1);
613
+ sc += attackScore(active, na, fin, tgt);
614
+ if (sc > bestTotal) {
615
+ bestTotal = sc;
616
+ for (int i = 0; i < 3; i++) bestFin[i] = -1;
617
+ bestTgt.assign(3, -1);
618
+ for (int i = 0; i < na; i++) { bestFin[active[i]] = fin[i]; bestTgt[active[i]] = tgt[i]; }
619
+ }
620
+ }
621
+
622
+ // ---- 输出 ----
623
+ string out = to_string(g->turn);
624
+ string notes[3];
625
+ for (int k = 0; k < 3; k++) {
626
+ int id = ids[k];
627
+ if (plans[k].role == R_DEAD) { out += " S -"; notes[k] = "dead"; continue; }
628
+ int fin = bestFin[k];
629
+ char mvc = 'S';
630
+ int sx = pos[id] % SZ, sy = pos[id] / SZ, fx = fin % SZ, fy = fin / SZ;
631
+ if (fx == sx && fy == sy) mvc = 'S';
632
+ else if (fy == sy - 1) mvc = 'U';
633
+ else if (fy == sy + 1) mvc = 'D';
634
+ else if (fx == sx - 1) mvc = 'L';
635
+ else if (fx == sx + 1) mvc = 'R';
636
+
637
+ string act = "-";
638
+ if (!imm[id] && g->units[id].flag < 0) {
639
+ int tgt = bestTgt[k];
640
+ if (flagAt[fin] >= 0 && safePickup(id, fin)) act = "P";
641
+ else if (tgt >= 0) act = to_string(tgt);
642
+ }
643
+ out += " "; out += mvc; out += " "; out += act;
644
+ notes[k] = plans[k].role == R_CARRY ? "carry" : plans[k].role == R_RUN ? "run" :
645
+ plans[k].role == R_HUNT ? "hunt" : plans[k].role == R_ESCORT ? "esc" : "pos";
646
+ }
647
+ out += " #";
648
+ for (int k = 0; k < 3; k++) { if (k) out += ";"; out += notes[k]; }
649
+ return out;
650
+ }
651
+
652
+ double attackScore(const vector<int>& active, int na, int fin[3], vector<int>& tgt) {
653
+ // 出手没有任何站位代价(伤害只由位置决定,且阵亡者的攻击照样生效),
654
+ // 所以只要结算后够得着就打;距离 1 几乎必中,距离 2 约 4–6 成命中,按期望价值取舍。
655
+ vector<vector<pair<int, double>>> cand(3); // (敌人, 命中概率)
656
+ for (int k = 0; k < 3; k++) tgt[k] = -1;
657
+ for (int i = 0; i < na; i++) {
658
+ int k = active[i], id = 3 * ME + k;
659
+ if (imm[id] || g->units[id].flag >= 0) continue;
660
+ for (auto& e : g->units) {
661
+ if (e.team == ME || pos[e.id] < 0 || imm[e.id]) continue;
662
+ if (!canAttack(fin[i], pos[e.id])) continue;
663
+ int d = md(fin[i], pos[e.id]);
664
+ double p = (d <= 1) ? 1.0 : 0.50;
665
+ if (d > 1) {
666
+ int pr = pred[e.id];
667
+ if (pr >= 0 && canAttack(fin[i], pr)) p = 0.68; // 预判一致,更有把握
668
+ if (pr == pos[e.id]) p = 0.85; // 预计它不动
669
+ }
670
+ cand[k].push_back({e.id, p});
671
+ }
672
+ if (cand[k].size() > 6) {
673
+ const auto& U = g->units;
674
+ sort(cand[k].begin(), cand[k].end(), [&U](const pair<int, double>& a, const pair<int, double>& b) {
675
+ return U[a.first].hp < U[b.first].hp;
676
+ });
677
+ cand[k].resize(6);
678
+ }
679
+ }
680
+ int sz[3] = {1, 1, 1};
681
+ for (int k = 0; k < 3; k++) sz[k] = (int)cand[k].size() + 1;
682
+ int tot = sz[0] * sz[1] * sz[2];
683
+ if (tot <= 1) return 0;
684
+ double best = 0; vector<int> bestT(3, -1);
685
+ for (int c = 0; c < tot; c++) {
686
+ int cc = c, pick[3] = {-1, -1, -1};
687
+ double pp[3] = {0, 0, 0};
688
+ for (int k = 0; k < 3; k++) {
689
+ int v = cc % sz[k]; cc /= sz[k];
690
+ if (v > 0) { pick[k] = cand[k][v - 1].first; pp[k] = cand[k][v - 1].second; }
691
+ }
692
+ vector<pair<int, double>> acc;
693
+ for (int k = 0; k < 3; k++) if (pick[k] >= 0) {
694
+ bool f2 = false;
695
+ for (auto& pr : acc) if (pr.first == pick[k]) { pr.second += pp[k]; f2 = true; break; }
696
+ if (!f2) acc.push_back({pick[k], pp[k]});
697
+ }
698
+ double val = 0;
699
+ for (auto& pr : acc) {
700
+ int e = pr.first;
701
+ double ed = pr.second * DMG; // 期望伤害
702
+ int hp = g->units[e].hp;
703
+ double r = min(1.0, ed / hp); // 距击杀线的进度(凸函数 => 鼓励集火)
704
+ double v = DMGVAL * ed + KILLVAL * pow(r, FOCUSP);
705
+ if (g->units[e].flag >= 0) v += CARRBON * r;
706
+ val += v;
707
+ }
708
+ if (val > best) { best = val; for (int k = 0; k < 3; k++) bestT[k] = pick[k]; }
709
+ }
710
+ for (int k = 0; k < 3; k++) tgt[k] = bestT[k];
711
+ return best;
712
+ }
713
+ };
714
+
715
+ int main() {
716
+ ios::sync_with_stdio(false);
717
+ Game g;
718
+ AI ai;
719
+ ai.g = &g;
720
+ while (read_turn(g)) {
721
+ string s;
722
+ try { s = ai.decide(); } catch (...) { s = to_string(g.turn) + " S - S - S -"; }
723
+ cout << s << endl;
724
+ }
725
+ return 0;
726
+ }