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,41 @@
1
+ # 夺旗
2
+
3
+ 编写 `submission/bot.cpp`:一个程序统一控制己方 3 个角色,支持 2–15 个阵营同场。完整规则和输入输出格式见 `RULES.md`。
4
+
5
+ 解压时开始计时。你有 60 分钟,只看最终 bot 的强度,提前交卷不加分;可以适当超时,但到 90 分钟会强制交卷。
6
+
7
+ ## 文件
8
+
9
+ | 文件 | 用途 |
10
+ |---|---|
11
+ | `RULES.md` | 规则与程序接口 |
12
+ | `submission/bot.cpp` | 你的程序。现在是一个模板:已经写好完整的输入解析,角色原地不动 |
13
+ | `baseline.exe` | 中等强度的练习对手,思路常规,没有提供源码,请不要反编译 |
14
+ | `arena.py`、`engine.py`、`core.py` | 本地对战用的裁判,与正式比赛相同 |
15
+ | `viewer.html` | 回放网页模板 |
16
+ | `clock.py` | 查看已用时间 |
17
+ | `run.cmd`、`run.sh` | 找到 Python 并运行上面的脚本 |
18
+
19
+ ## 常用命令
20
+
21
+ 在本目录运行。下面以 PowerShell 和命令提示符为例,写作 `.\run.cmd`;在 bash 里(包括 Windows 上的 Git Bash 和 Linux)把它换成 `sh run.sh`,后面的参数不变。
22
+
23
+ ```text
24
+ .\run.cmd clock.py 已用时间
25
+ .\run.cmd arena.py check 检查环境
26
+ .\run.cmd arena.py play submission/bot.cpp baseline --seed 7
27
+ .\run.cmd arena.py play submission/bot.cpp baseline baseline baseline baseline --out five
28
+ .\run.cmd arena.py show replays/five.json 120 --around 2 看第 118–122 回合
29
+ .\run.cmd arena.py batch submission/bot.cpp baseline baseline --games 12
30
+ ```
31
+
32
+ - `.cpp` 程序会先自动编译,结果按源码缓存在 `build/`,源码不变就不重复编译。编译出错时会显示编译器的报错。
33
+ - `play` 打一局,打印每个程序的得分、名次、每回合平均和最慢用时,并把回放存成 `replays/名字.json` 和 `replays/名字.html`。网页可以逐回合播放。
34
+ - `show` 在终端里打印棋盘、所有角色的位置、血量和携旗情况、事件,以及各方的指令。
35
+ - `batch` 连续打多局并汇总每个程序的平均得分、平均名次、第一名次数、故障和最慢用时,不保存回放。它和正式比赛一样,每张地图把座位完整轮换一圈(n 方就是 n 局),所以局数最好取人数的整数倍。
36
+ - `--seed` 只决定地图。旗子每次出现在哪个旗点每局都不同,所以同一个种子重打,结果也可能不同。
37
+ - 程序可以重复出场,比如自己和自己对打。
38
+
39
+ ## 提交
40
+
41
+ 最终代码放在 `workspace/submission/bot.cpp`,结束时用几句话说明你的策略。
@@ -0,0 +1,119 @@
1
+ # 多阵营夺旗 · 规则 v1.0
2
+
3
+ 2–15 个阵营同场,每个阵营由一个程序控制 3 个角色。把中立旗送回自己的基地,每送回一面得 1 分。400 回合后按得分排名,同分并列。
4
+
5
+ ## 地图
6
+
7
+ - 正方形格子地图,边长:2 方 23,3–5 方 31,6–10 方 41,11–15 方 49。四周是墙,场内散布单格柱子,也是墙。整张地图公开。
8
+ - **基地**:每个阵营一块 3×3 区域。所有基地排成一圈,到地图中心的距离相同,相邻基地的间距大致相等。任何角色都可以走进任何基地。
9
+ - **旗点**:每个基地前方各一个,再加地图正中心一个,共"阵营数 + 1"个。每个基地到自家旗点、到中心的距离都相同。
10
+ - **旗子**:共 max(2, ⌈阵营数 / 2⌉) 面,没有归属。开局和每次回位时,旗子随机出现在一个没有旗子的旗点上。这个随机与地图生成无关,无法预测。
11
+
12
+ ## 每回合
13
+
14
+ 每个角色选一个**移动**(上、下、左、右、不动)和一个**动作**(攻击、拾旗、丢旗、不做事)。所有程序同时提交,然后按下面的顺序结算:
15
+
16
+ 1. 到时间的旗子回位,到时间的阵亡角色重生
17
+ 2. 移动
18
+ 3. 攻击,阵亡
19
+ 4. 拾旗
20
+ 5. 丢旗
21
+ 6. 交旗
22
+
23
+ ### 移动
24
+
25
+ 上下左右走一格,或原地不动。走向墙等于不动。结算后任何两个角色都不在同一格:
26
+
27
+ - 多个角色要进同一格:都不动。
28
+ - 走进别人正在离开的格子:可以;如果对方没走成,你也不动。
29
+ - 两个角色对换位置:同阵营可以;不同阵营都不动。
30
+ - 三个及以上角色绕圈移动:可以。
31
+
32
+ ### 攻击
33
+
34
+ - 攻击要指定目标(敌方角色的 id),按双方移动后的位置判断能否打到。
35
+ - 射程:横纵距离之和不超过 2。墙的遮挡只有两种情况:隔一格直线攻击(如横向相距 2)时,中间那格不能是墙;斜向攻击(横纵各差 1)时,两个拐角格至少有一个不是墙。紧挨着的攻击不受墙影响。
36
+ - 每次命中造成 34 点伤害。生命值 100,不会自然回复,所以 3 次命中必定阵亡。
37
+ - 所有攻击同时生效,双方可以同回合互相击杀。
38
+ - 携旗的角色不能攻击。刚重生的角色,在重生的那个回合既不能攻击,也不会被攻击。
39
+
40
+ ### 旗子
41
+
42
+ - 拾旗:和旗子在同一格时拿起。每人最多拿一面;同一格有几面时拿编号最小的。
43
+ - 携旗角色阵亡时,旗子掉在原地,任何阵营都能捡。
44
+ - 丢旗:主动放下旗子。拾旗先于丢旗结算,所以队友最早下一回合才能捡起。
45
+ - 地上的旗子 15 回合无人拾取,会回到一个随机的空旗点。
46
+ - 回合结束时携旗角色站在自己的基地里,即交旗得 1 分。这面旗离场 8 回合后,重新出现在一个随机的空旗点。
47
+
48
+ ### 阵亡与重生
49
+
50
+ 阵亡 10 回合后在自己的基地重生,满血,优先占离地图中心近的空格;基地被站满(包括被敌人站住)时推迟到有空格为止。可以提前给即将重生的角色下指令,它在重生的那个回合就会移动和拾旗。
51
+
52
+ ## 程序接口
53
+
54
+ - **语言**:C++20,只能用标准库。提交一个文件 `submission/bot.cpp`,用 `g++ -std=c++20 -O2` 编译(Windows 另加 `-static`)。
55
+ - **通信**:每回合从标准输入读一个文本块,向标准输出写一行回复,写完立即刷新(例如用 `endl`)。程序在整局比赛中持续运行,可以在内存里保存状态。调试信息写到标准错误。
56
+ - **时限**:第一回合 2 秒(含程序启动),之后每回合 50 毫秒,从发出输入到收到完整回复计时。超时或回复格式错误时,这一回合按全部原地不动处理,迟到的回复会被丢弃。连续失败 3 次,或者程序退出,本局剩余回合都按原地不动处理。
57
+ - 程序只能通过标准输入输出与裁判通信,不得读写文件、联网或启动其他程序。
58
+
59
+ `submission/bot.cpp` 模板已经写好了完整的输入解析,可以直接沿用。
60
+
61
+ ### 输入
62
+
63
+ 第一回合先收到一次开局信息,之后每回合收到一个以 `END` 结尾的块。数字之间用空格分隔,每项占一行:
64
+
65
+ ```text
66
+ INIT 阵营数 你的阵营 地图边长 总回合数 生命 伤害 重生回合数 旗子无人拾取回位回合数 交旗后离场回合数
67
+ MAP
68
+ (地图边长行,# 墙,. 空地)
69
+ BASES 阵营数
70
+ x y (每行一个阵营的基地中心,基地是以它为中心的 3×3)
71
+ SPOTS 旗点数
72
+ x y (每行一个旗点)
73
+ ```
74
+
75
+ ```text
76
+ TURN t (即将结算的回合,从 1 开始)
77
+ SCORE 各阵营得分
78
+ UNITS 角色数
79
+ U id 阵营 x y 生命 携带的旗 重生回合
80
+ FLAGS 旗子数
81
+ F id 状态 x y 携旗角色 回位回合
82
+ EVENTS 事件数
83
+ E 类型 ... (上一回合发生的事件,见下表)
84
+ END
85
+ ```
86
+
87
+ - 坐标以左上角为 (0, 0),x 向右增大,y 向下增大。阵营 k 的角色 id 为 3k、3k+1、3k+2。
88
+ - **角色**:阵亡时 x、y 为 -1;没带旗时"携带的旗"为 -1;活着时"重生回合"为 -1。
89
+ - **旗子**:状态为 `home`(在旗点)、`dropped`(在地上)、`carried`(被携带)、`cooldown`(交旗后离场)。后两种状态下 x、y 为 -1;没有携旗角色、没有回位回合时对应值为 -1。
90
+ - 重生回合或回位回合等于 t 时,重生或回位就发生在本回合开头。
91
+
92
+ | 事件 | 格式 |
93
+ |---|---|
94
+ | 旗子回位 | `E return 旗` |
95
+ | 重生 | `E respawn 角色 x y` |
96
+ | 命中 | `E attack 攻击者 目标` |
97
+ | 阵亡 | `E death 角色 x y 掉落的旗 击杀者人数 击杀者...`(没掉旗为 -1) |
98
+ | 拾旗 / 丢旗 | `E pickup 角色 旗`、`E drop 角色 旗` |
99
+ | 交旗 | `E capture 角色 阵营 旗` |
100
+
101
+ ### 输出
102
+
103
+ 一行:本回合编号,然后按 id 从小到大给自己的 3 个角色各写一个移动和一个动作:
104
+
105
+ ```text
106
+ t 移动 动作 移动 动作 移动 动作 [# 注释;注释;注释]
107
+ ```
108
+
109
+ - **移动**:`U` 上,`D` 下,`L` 左,`R` 右,`S` 原地不动。
110
+ - **动作**:`-` 什么都不做,`P` 拾旗,`X` 丢旗,一个数字表示攻击这个 id 的角色。
111
+ - **注释**(可选):`#` 之后用分号分隔,依次对应 3 个角色,每条最多 20 个字,只显示在回放里,例如 `# 抢旗;护送;拦截`。
112
+ - 例:`37 R 4 U P S -` 表示第 37 回合,第一个角色右移并攻击 4 号,第二个角色上移并拾旗,第三个角色不动。
113
+ - 阵亡角色也要写,除非它本回合重生,否则会被忽略。回合编号不对、字段数量不对或出现未定义的字母,都算格式错误。
114
+
115
+ ## 正式比赛
116
+
117
+ - 参赛的是其他模型写的程序,外加一个中等强度的基准。
118
+ - 覆盖从 2 方到全员的各种人数,每种人数权重相同。地图种子不公开;同一组程序会在同一张地图上轮换全部座位。
119
+ - 每局按得分排名,名次拆成两两胜负,用 Bradley–Terry 模型拟合实力值,基准为 0 分。
@@ -0,0 +1,186 @@
1
+ """本地对战与复盘。
2
+
3
+ arena.py check 检查环境
4
+ arena.py play 程序 程序 ... [--seed N] [--out 名字]
5
+ 对战 2–15 个程序(.cpp 会自动编译),baseline 表示练习基准
6
+ arena.py batch 程序 程序 ... --games N [--seed N] [--workers W]
7
+ 多局对战并汇总,每张地图轮换全部座位
8
+ arena.py show 回放.json 回合 [--around K] 打印该回合前后 K 回合的棋盘、角色和指令
9
+ """
10
+ import argparse
11
+ import json
12
+ import os
13
+ import sys
14
+ from pathlib import Path
15
+
16
+ HERE = Path(__file__).resolve().parent
17
+ sys.path.insert(0, str(HERE))
18
+ import core # noqa: E402
19
+ from engine import VERSION, Game # noqa: E402
20
+
21
+ TEAM_CHARS = "0123456789abcde"
22
+
23
+
24
+ BUILD = HERE / "build"
25
+
26
+
27
+ def program(spec):
28
+ """返回运行命令。.cpp 会先编译(结果缓存在 build/,源码不变就不重复编译)。"""
29
+ if spec == "baseline": # 开源版:没有编译好的基准时,用 baseline.cpp 源码自动编译
30
+ path = HERE / ("baseline.exe" if os.name == "nt" else "baseline")
31
+ if not path.is_file():
32
+ path = HERE / "baseline.cpp"
33
+ else:
34
+ path = Path(spec).resolve()
35
+ if not path.is_file():
36
+ raise SystemExit(f"找不到程序:{spec}")
37
+ try:
38
+ return core.command_for(path, BUILD)
39
+ except RuntimeError as exc:
40
+ raise SystemExit(f"{spec}:{exc}")
41
+
42
+
43
+ def names_for(specs):
44
+ names, seen = [], {}
45
+ for s in specs:
46
+ stem = "baseline" if s == "baseline" else Path(s).stem
47
+ seen[stem] = seen.get(stem, 0) + 1
48
+ names.append(stem if specs.count(s) == 1 and seen[stem] == 1 else f"{stem}#{seen[stem]}")
49
+ return names
50
+
51
+
52
+ def play(specs, seed, out):
53
+ if not 2 <= len(specs) <= 15:
54
+ raise SystemExit("需要 2–15 个程序")
55
+ programs = [program(s) for s in specs]
56
+ names = names_for(specs)
57
+ result, replay = core.run_match(Game, programs, names, seed)
58
+ path = HERE / "replays" / out
59
+ core.save_replay(replay, path, HERE / "viewer.html")
60
+ ranks = [1 + sum(s > x for s in result["score"]) for x in result["score"]]
61
+ print(f"{VERSION} {len(specs)} 方 种子 {seed}")
62
+ for i, (n, b) in enumerate(zip(names, result["bots"])):
63
+ extra = f" 故障:{b['dead']}" if b["dead"] else f" 失败 {len(b['faults'])} 次" if b["faults"] else ""
64
+ print(f" {i}号位 {n:14s} 交旗 {result['score'][i]:3d} 第 {ranks[i]} 名 "
65
+ f"每回合平均 {b['avg_ms']:.1f}ms、最慢 {b['max_ms']:.1f}ms(首回合 {b['first_ms']:.0f}ms){extra}")
66
+ for line in b["stderr_tail"]:
67
+ print(f" stderr: {line}")
68
+ print(f"回放:{path.with_suffix('.html')}(网页) {path.with_suffix('.json')}(数据)")
69
+
70
+
71
+ def _one(job):
72
+ programs, names, seed = job
73
+ result, _ = core.run_match(Game, programs, names, seed, record=False)
74
+ return names, result["score"], result["bots"]
75
+
76
+
77
+ def batch(specs, games, seed, workers):
78
+ """多局对战:每张地图把座位完整轮换一圈(n 方就是 n 局),再换下一张地图。"""
79
+ import os
80
+ from concurrent.futures import ProcessPoolExecutor
81
+ n = len(specs)
82
+ if not 2 <= n <= 15:
83
+ raise SystemExit("需要 2–15 个程序")
84
+ programs, names = [program(s) for s in specs], names_for(specs)
85
+ jobs = []
86
+ for k in range(games):
87
+ r = k % n
88
+ jobs.append((programs[r:] + programs[:r], names[r:] + names[:r], seed + k // n))
89
+ workers = workers or max(1, min(8, (os.cpu_count() or 4) // 2 // n))
90
+ stats = {x: {"games": 0, "score": 0, "rank": 0, "first": 0, "faults": 0, "max_ms": 0.0} for x in names}
91
+ with ProcessPoolExecutor(workers) as pool:
92
+ for seats, score, bots in pool.map(_one, jobs):
93
+ ranks = [1 + sum(s > x for s in score) for x in score]
94
+ for i, x in enumerate(seats):
95
+ st = stats[x]
96
+ st["games"] += 1
97
+ st["score"] += score[i]
98
+ st["rank"] += ranks[i]
99
+ st["first"] += ranks[i] == 1
100
+ st["faults"] += len(bots[i]["faults"]) + (1 if bots[i]["dead"] else 0)
101
+ st["max_ms"] = max(st["max_ms"], bots[i]["max_ms"])
102
+ print(f"{VERSION} {n} 方 {games} 局(地图种子 {seed}–{seed + (games - 1) // n},每张图轮换全部座位) 并行 {workers}")
103
+ for x, st in sorted(stats.items(), key=lambda kv: -kv[1]["score"]):
104
+ g = st["games"]
105
+ print(f" {x:14s} 平均交旗 {st['score'] / g:5.1f} 平均名次 {st['rank'] / g:4.2f} "
106
+ f"第一 {st['first']}/{g} 故障 {st['faults']} 最慢 {st['max_ms']:.0f}ms")
107
+ if games % n:
108
+ print(f"注意:局数不是 {n} 的整数倍,最后一张地图没有轮换完所有座位。")
109
+ print(f"并行局数越多,程序的每回合用时越容易被拉长;判断是否接近 {Game.TIME_LIMITS[1] * 1000:g} 毫秒的限时,请以单局 play 的结果为准。")
110
+
111
+
112
+ def show(path, turn, around):
113
+ data = json.loads(Path(path).read_text(encoding="utf-8"))
114
+ head, frames = data["header"], data["frames"]
115
+ walls = head["map"]
116
+ base_of = {tuple(p): t for t, b in enumerate(head["bases"]) for p in b}
117
+ for f in frames:
118
+ if abs(f["turn"] - turn) > around:
119
+ continue
120
+ grid = [list(row) for row in walls]
121
+ for (x, y), t in base_of.items():
122
+ grid[y][x] = "+"
123
+ for fl in f["flags"]:
124
+ if fl["pos"]:
125
+ grid[fl["pos"][1]][fl["pos"][0]] = "*"
126
+ for u in f["units"]:
127
+ if u["pos"]:
128
+ grid[u["pos"][1]][u["pos"][0]] = TEAM_CHARS[u["id"] // 3]
129
+ print(f"\n===== 第 {f['turn']} 回合结束 得分 {f['score']} =====")
130
+ print("图例:# 墙 + 基地 * 地上的旗 数字/字母 = 该阵营的角色\n")
131
+ print("\n".join("".join(r) for r in grid))
132
+ print("\n角色:" + " ".join(
133
+ f"{u['id']}({TEAM_CHARS[u['id'] // 3]})" +
134
+ (f"@{u['pos'][0]},{u['pos'][1]} {u['hp']}血" + (f" 携旗{u['flag']}" if u["flag"] is not None else "")
135
+ if u["pos"] else " 阵亡") for u in f["units"]))
136
+ print("事件:" + (";".join(json.dumps(e, ensure_ascii=False) for e in f["events"]) or "无"))
137
+ for t, cmds in sorted(f["orders"].items(), key=lambda kv: int(kv[0])):
138
+ print(f"指令 {t}:{json.dumps(cmds, ensure_ascii=False)}")
139
+ for fault in f.get("faults", []):
140
+ print(f"故障 {fault['team']}:{fault['reason']}")
141
+
142
+
143
+ def check():
144
+ if sys.version_info < (3, 10):
145
+ raise SystemExit(f"需要 Python 3.10 以上,当前是 {sys.version.split()[0]}")
146
+ compiler = core.find_compiler()
147
+ if not compiler:
148
+ raise SystemExit("没有找到 C++ 编译器(g++ 或 clang++)。请把情况告诉组织方。")
149
+ result, _ = core.run_match(Game, [program("baseline"), program(str(HERE / "submission" / "bot.cpp"))],
150
+ ["baseline", "bot"], 1, record=False)
151
+ bad = [b for b in result["bots"] if b["faults"] or b["dead"]]
152
+ if bad:
153
+ raise SystemExit(f"试跑出错:{bad}")
154
+ print(f"正常:Python {sys.version.split()[0]},编译器 {compiler},模板和基准都能运行。")
155
+
156
+
157
+ def main():
158
+ ap = argparse.ArgumentParser(description="夺旗本地对战与复盘")
159
+ sub = ap.add_subparsers(dest="cmd", required=True)
160
+ sub.add_parser("check")
161
+ p = sub.add_parser("play")
162
+ p.add_argument("programs", nargs="+")
163
+ p.add_argument("--seed", type=int, default=1)
164
+ p.add_argument("--out", default="latest")
165
+ p = sub.add_parser("batch")
166
+ p.add_argument("programs", nargs="+")
167
+ p.add_argument("--games", type=int, required=True)
168
+ p.add_argument("--seed", type=int, default=1)
169
+ p.add_argument("--workers", type=int, default=0)
170
+ p = sub.add_parser("show")
171
+ p.add_argument("replay")
172
+ p.add_argument("turn", type=int)
173
+ p.add_argument("--around", type=int, default=0)
174
+ a = ap.parse_args()
175
+ if a.cmd == "check":
176
+ check()
177
+ elif a.cmd == "play":
178
+ play(a.programs, a.seed, a.out)
179
+ elif a.cmd == "batch":
180
+ batch(a.programs, a.games, a.seed, a.workers)
181
+ else:
182
+ show(a.replay, a.turn, a.around)
183
+
184
+
185
+ if __name__ == "__main__":
186
+ main()
@@ -0,0 +1,257 @@
1
+ // 练习基准(中等):常规思路,不做对手建模和多步模拟。
2
+ // - 抢旗:比较我方和敌方谁先到,抢得到的旗才去,拿到就沿最短路回家。
3
+ // - 截杀:追得上的敌方携旗者就去追,在自家附近的优先。
4
+ // - 没事做时守在离家最近的空旗点。
5
+ // - 移动时以少打多就绕开;攻击按射程规则选目标,队友集火同一个。
6
+ #include <bits/stdc++.h>
7
+ using namespace std;
8
+
9
+ struct Unit { int id, team, x, y, hp, flag, respawn; };
10
+ struct Flag { int id; string status; int x, y, holder, ret; };
11
+
12
+ int n, me, S, turns, HP, DMG;
13
+ vector<string> grid;
14
+ vector<pair<int, int>> baseCenter, spots;
15
+ vector<vector<int>> homeField; // 各阵营基地的距离场
16
+ map<int, vector<int>> fieldCache;
17
+
18
+ inline int idx(int x, int y) { return y * S + x; }
19
+ inline bool wall(int x, int y) { return x < 0 || y < 0 || x >= S || y >= S || grid[y][x] == '#'; }
20
+ inline int manh(int a, int b) { return abs(a % S - b % S) + abs(a / S - b / S); }
21
+ const int DX[5] = {0, 0, -1, 1, 0}, DY[5] = {-1, 1, 0, 0, 0};
22
+ const char* MV[5] = {"U", "D", "L", "R", "S"};
23
+
24
+ vector<int> bfs(const vector<int>& src) {
25
+ vector<int> d(S * S, INT_MAX);
26
+ deque<int> q;
27
+ for (int c : src) { d[c] = 0; q.push_back(c); }
28
+ while (!q.empty()) {
29
+ int c = q.front(); q.pop_front();
30
+ for (int k = 0; k < 4; k++) {
31
+ int nx = c % S + DX[k], ny = c / S + DY[k];
32
+ if (wall(nx, ny)) continue;
33
+ int nc = idx(nx, ny);
34
+ if (d[nc] == INT_MAX) { d[nc] = d[c] + 1; q.push_back(nc); }
35
+ }
36
+ }
37
+ return d;
38
+ }
39
+ const vector<int>& field(int c) {
40
+ auto it = fieldCache.find(c);
41
+ if (it != fieldCache.end()) return it->second;
42
+ if (fieldCache.size() > 256) fieldCache.clear();
43
+ return fieldCache[c] = bfs({c});
44
+ }
45
+ int dist(int a, int b) { return field(b)[a]; }
46
+
47
+ // 射程 2,墙会挡:直线隔一格要求中间不是墙,斜对角要求两个拐角至少一个不是墙
48
+ bool canHit(int a, int b) {
49
+ int ax = a % S, ay = a / S, bx = b % S, by = b / S, dx = bx - ax, dy = by - ay;
50
+ int d = abs(dx) + abs(dy);
51
+ if (d == 0 || d > 2) return false;
52
+ if (d == 1) return true;
53
+ if (dx == 0) return !wall(ax, ay + dy / 2);
54
+ if (dy == 0) return !wall(ax + dx / 2, ay);
55
+ return !wall(ax + dx, ay) || !wall(ax, ay + dy);
56
+ }
57
+
58
+ int main() {
59
+ ios::sync_with_stdio(false);
60
+ string w;
61
+ while (cin >> w) {
62
+ if (w == "INIT") {
63
+ int re, fr, fc;
64
+ cin >> n >> me >> S >> turns >> HP >> DMG >> re >> fr >> fc;
65
+ } else if (w == "MAP") {
66
+ grid.assign(S, "");
67
+ for (auto& r : grid) cin >> r;
68
+ } else if (w == "BASES") {
69
+ int k; cin >> k; baseCenter.resize(k);
70
+ for (auto& b : baseCenter) cin >> b.first >> b.second;
71
+ homeField.resize(k);
72
+ for (int t = 0; t < k; t++) {
73
+ vector<int> cells;
74
+ for (int dx = -1; dx <= 1; dx++) for (int dy = -1; dy <= 1; dy++)
75
+ cells.push_back(idx(baseCenter[t].first + dx, baseCenter[t].second + dy));
76
+ homeField[t] = bfs(cells);
77
+ }
78
+ } else if (w == "SPOTS") {
79
+ int k; cin >> k; spots.resize(k);
80
+ for (auto& s : spots) cin >> s.first >> s.second;
81
+ } else if (w == "TURN") {
82
+ int t; cin >> t;
83
+ string key; int k;
84
+ cin >> key; for (int i = 0, s; i < n; i++) cin >> s;
85
+ cin >> key >> k; vector<Unit> units(k);
86
+ for (auto& u : units) { string tag; cin >> tag >> u.id >> u.team >> u.x >> u.y >> u.hp >> u.flag >> u.respawn; }
87
+ cin >> key >> k; vector<Flag> flags(k);
88
+ for (auto& f : flags) { string tag; cin >> tag >> f.id >> f.status >> f.x >> f.y >> f.holder >> f.ret; }
89
+ cin >> key >> k; string rest; getline(cin, rest);
90
+ for (int i = 0; i < k; i++) getline(cin, rest);
91
+ cin >> key; // END
92
+
93
+ vector<int> pos(units.size(), -1);
94
+ for (auto& u : units) if (u.x >= 0) pos[u.id] = idx(u.x, u.y);
95
+ vector<int> foes, mine;
96
+ for (auto& u : units) {
97
+ if (u.x < 0) continue;
98
+ (u.team == me ? mine : foes).push_back(u.id);
99
+ }
100
+ auto enemyETA = [&](int c) {
101
+ int best = INT_MAX;
102
+ for (int e : foes) if (units[e].flag < 0) best = min(best, dist(pos[e], c));
103
+ return best;
104
+ };
105
+
106
+ // ---------- 任务分配(贪心) ----------
107
+ struct Task { int goal; double value; int cap; };
108
+ vector<Task> tasks;
109
+ vector<pair<int, int>> cand; // (角色, 任务) 由价值排序
110
+ vector<vector<double>> val;
111
+ for (auto& f : flags) {
112
+ if (f.x < 0) continue;
113
+ tasks.push_back({idx(f.x, f.y), (double)f.ret, 1}); // value 字段暂存回位回合
114
+ }
115
+ int nFlagTasks = tasks.size();
116
+ for (int e : foes) {
117
+ if (units[e].flag < 0) continue;
118
+ tasks.push_back({pos[e], (double)e, 2}); // 直接追,value 暂存敌人编号
119
+ }
120
+ int nHunt = tasks.size() - nFlagTasks;
121
+ // 空旗点:离自家基地由近到远
122
+ vector<char> hasFlag(S * S, 0);
123
+ for (auto& f : flags) if (f.x >= 0) hasFlag[idx(f.x, f.y)] = 1;
124
+ for (auto& s : spots) if (!hasFlag[idx(s.first, s.second)]) tasks.push_back({idx(s.first, s.second), 0, 1});
125
+
126
+ vector<int> goal(mine.size(), -1);
127
+ {
128
+ vector<tuple<double, int, int>> scored; // (价值, 角色序号, 任务序号)
129
+ for (int a = 0; a < (int)mine.size(); a++) {
130
+ auto& u = units[mine[a]];
131
+ if (u.flag >= 0) continue;
132
+ int p = pos[u.id];
133
+ for (int j = 0; j < (int)tasks.size(); j++) {
134
+ auto& tk = tasks[j];
135
+ double v = 0;
136
+ if (j < nFlagTasks) {
137
+ int my = dist(p, tk.goal), en = enemyETA(tk.goal);
138
+ int ret = (int)tk.value;
139
+ if (ret >= 0 && t + my > ret) continue; // 赶到前就回位了
140
+ int home = homeField[me][tk.goal];
141
+ if (t + my + home > turns) continue; // 来不及送回
142
+ double win = my < en ? 1.0 : my == en ? 0.5 : 0.15;
143
+ v = 10.0 * win / (my + home + 2);
144
+ } else if (j < nFlagTasks + nHunt) {
145
+ int e = (int)tk.value, eh = homeField[units[e].team][pos[e]];
146
+ int my = dist(p, pos[e]);
147
+ if (my > eh + 3) continue; // 追不上
148
+ v = 6.0 / (my + 3);
149
+ if (homeField[me][pos[e]] < 12) v *= 1.5; // 在自家附近更值得拦
150
+ } else {
151
+ int my = dist(p, tk.goal);
152
+ v = 1.0 / (my + homeField[me][tk.goal] + 6);
153
+ }
154
+ scored.push_back({v, a, j});
155
+ }
156
+ }
157
+ sort(scored.rbegin(), scored.rend());
158
+ vector<int> used(tasks.size(), 0);
159
+ for (auto& [v, a, j] : scored) {
160
+ if (goal[a] >= 0 || used[j] >= tasks[j].cap) continue;
161
+ goal[a] = tasks[j].goal; used[j]++;
162
+ }
163
+ }
164
+
165
+ // ---------- 移动 ----------
166
+ // threat[c]:下回合可能打到 c 的敌人数(敌人移动一步后在射程内)
167
+ vector<int> threat(S * S, 0);
168
+ for (int e : foes) {
169
+ if (units[e].flag >= 0) continue;
170
+ set<int> hit;
171
+ for (int m = 0; m < 5; m++) {
172
+ int ex = pos[e] % S + DX[m], ey = pos[e] / S + DY[m];
173
+ if (wall(ex, ey)) continue;
174
+ int ec = idx(ex, ey);
175
+ for (int dy = -2; dy <= 2; dy++) for (int dx = -2; dx <= 2; dx++) {
176
+ int x = ex + dx, y = ey + dy;
177
+ if (wall(x, y)) continue;
178
+ if (canHit(ec, idx(x, y))) hit.insert(idx(x, y));
179
+ }
180
+ }
181
+ for (int c : hit) threat[c]++;
182
+ }
183
+ vector<int> occupied(S * S, -1);
184
+ for (auto& u : units) if (u.x >= 0) occupied[pos[u.id]] = u.id;
185
+ vector<char> claimed(S * S, 0);
186
+ vector<int> newPos(mine.size());
187
+ vector<int> order(mine.size());
188
+ iota(order.begin(), order.end(), 0);
189
+ stable_sort(order.begin(), order.end(), [&](int a, int b) { return units[mine[a]].flag > units[mine[b]].flag; });
190
+ for (int a : order) {
191
+ auto& u = units[mine[a]];
192
+ int p = pos[u.id];
193
+ const vector<int>& gf = u.flag >= 0 ? homeField[me] : (goal[a] >= 0 ? field(goal[a]) : homeField[me]);
194
+ // 附近的敌我人数
195
+ int allies = 0, enemies = 0;
196
+ for (int b : mine) if (manh(pos[b], p) <= 3 && units[b].flag < 0) allies++;
197
+ for (int e : foes) if (manh(pos[e], p) <= 3 && units[e].flag < 0) enemies++;
198
+ bool outnumbered = enemies > allies;
199
+ double best = 1e18; int bm = 4;
200
+ for (int m = 0; m < 5; m++) {
201
+ int x = p % S + DX[m], y = p / S + DY[m];
202
+ if (wall(x, y)) continue;
203
+ int c = idx(x, y);
204
+ if (claimed[c]) continue;
205
+ if (c != p && occupied[c] >= 0 && units[occupied[c]].team != me) continue;
206
+ if (gf[c] == INT_MAX) continue;
207
+ double cost = gf[c] * 10.0;
208
+ if (u.flag >= 0) cost += threat[c] * 6.0;
209
+ else if (outnumbered) cost += threat[c] * 12.0;
210
+ else if (u.hp <= DMG) cost += threat[c] * 4.0;
211
+ if (c != p && occupied[c] >= 0) cost += 3; // 队友占着,可能走不动
212
+ if (cost < best) { best = cost; bm = m; }
213
+ }
214
+ int c = idx(p % S + DX[bm], p / S + DY[bm]);
215
+ newPos[a] = c; claimed[c] = 1;
216
+ u.respawn = bm; // 暂存移动方向
217
+ }
218
+
219
+ // ---------- 动作 ----------
220
+ vector<int> plan(units.size(), 0); // 已安排的伤害
221
+ string out = to_string(t);
222
+ for (int a = 0; a < (int)mine.size(); a++) {
223
+ auto& u = units[mine[a]];
224
+ int c = newPos[a];
225
+ string act = "-";
226
+ bool onFlag = false;
227
+ for (auto& f : flags) if (f.x >= 0 && idx(f.x, f.y) == c) onFlag = true;
228
+ if (u.flag < 0 && onFlag) act = "P";
229
+ else if (u.flag < 0) {
230
+ int bestE = -1; double bv = -1;
231
+ for (int e : foes) {
232
+ if (!canHit(c, pos[e])) continue;
233
+ if (plan[e] >= units[e].hp) continue; // 已经够打死了
234
+ double v = 1.0 + (units[e].flag >= 0 ? 5.0 : 0) + (units[e].hp - plan[e] <= DMG ? 3.0 : 0) + plan[e] / 50.0;
235
+ if (v > bv) { bv = v; bestE = e; }
236
+ }
237
+ if (bestE >= 0) { act = to_string(bestE); plan[bestE] += DMG; }
238
+ }
239
+ out += string(" ") + MV[u.respawn] + " " + act;
240
+ }
241
+ // 阵亡的角色原地待命
242
+ string full = to_string(t);
243
+ {
244
+ istringstream in(out); string tok; in >> tok;
245
+ vector<string> parts;
246
+ while (in >> tok) parts.push_back(tok);
247
+ int pi = 0;
248
+ for (auto& u : units) {
249
+ if (u.team != me) continue;
250
+ if (u.x < 0) full += " S -";
251
+ else { full += " " + parts[pi] + " " + parts[pi + 1]; pi += 2; }
252
+ }
253
+ }
254
+ cout << full << endl;
255
+ }
256
+ }
257
+ }
@@ -0,0 +1,15 @@
1
+ """查看开发用时:从解压出本文件的那一刻算起。只读,不写任何文件。"""
2
+ import time
3
+ from pathlib import Path
4
+
5
+ TARGET, LIMIT = 60, 90 # 分钟
6
+
7
+ st = Path(__file__).resolve().stat()
8
+ start = getattr(st, "st_birthtime", None) or st.st_ctime # Windows 为创建时间;Linux 解压时新建文件的 ctime 即解压时刻
9
+ used = max(0.0, time.time() - start) / 60
10
+ if used < TARGET:
11
+ print(f"已用 {used:.1f} 分钟,距 {TARGET} 分钟还有 {TARGET - used:.1f} 分钟({LIMIT} 分钟强制交卷)。")
12
+ elif used < LIMIT:
13
+ print(f"已用 {used:.1f} 分钟,超过 {TARGET} 分钟。可以继续改进,但 {LIMIT} 分钟会强制交卷,还剩 {LIMIT - used:.1f} 分钟。")
14
+ else:
15
+ print(f"已用 {used:.1f} 分钟,已到 {LIMIT} 分钟,请立即交卷。")