dsh-eval-guard 0.2.3 → 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 (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,79 @@
1
+ """赛程生成:任意参赛人数、任意单局人数。
2
+
3
+ 每张地图上,把参赛程序分成若干组,每组在同一张地图上把座位完整轮换一圈
4
+ (k 人组打 k 局),所以组内的座位优势完全抵消。分组时优先选出场少、
5
+ 彼此交手少的程序,使出场次数最多相差 1、两两交手次数尽量接近。
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import itertools
10
+ import random
11
+ from collections import Counter
12
+
13
+
14
+ def groups_for_map(players, k, appear, meet, rng):
15
+ """为一张地图选出若干 k 人组,覆盖所有程序至少一次。"""
16
+ if k == len(players):
17
+ order = players[:]
18
+ rng.shuffle(order)
19
+ return [order]
20
+ remaining = set(players)
21
+ groups = []
22
+ while remaining:
23
+ group = []
24
+ pool = sorted(remaining, key=lambda p: (appear[p], rng.random()))
25
+ while len(group) < k:
26
+ if not pool: # 最后一组不满时,从已分组的程序里补
27
+ pool = sorted((p for p in players if p not in group),
28
+ key=lambda p: (appear[p], rng.random()))
29
+ best = min(pool, key=lambda p: (appear[p], sum(meet[frozenset((p, q))] for q in group), rng.random()))
30
+ group.append(best)
31
+ pool.remove(best)
32
+ remaining.discard(best)
33
+ for a, b in itertools.combinations(group, 2):
34
+ meet[frozenset((a, b))] += k
35
+ for p in group:
36
+ appear[p] += k
37
+ rng.shuffle(group)
38
+ groups.append(group)
39
+ return groups
40
+
41
+
42
+ def make_schedule(players, sizes, maps_per_size, seed):
43
+ """返回局列表:{id, size, map_seed, map_index, group, seats}。seats[i] 是第 i 号位的程序名。"""
44
+ rng = random.Random(seed)
45
+ used = set()
46
+ games = []
47
+ for k in sizes:
48
+ if not 2 <= k <= len(players):
49
+ raise ValueError(f"单局人数 {k} 超出范围")
50
+ appear, meet = Counter(), Counter()
51
+ for m in range(maps_per_size):
52
+ map_seed = rng.randrange(1, 2**31)
53
+ while map_seed in used:
54
+ map_seed = rng.randrange(1, 2**31)
55
+ used.add(map_seed)
56
+ for g, group in enumerate(groups_for_map(players, k, appear, meet, rng)):
57
+ for shift in range(k):
58
+ games.append({"size": k, "map_seed": map_seed, "map_index": m, "group": g,
59
+ "seats": group[shift:] + group[:shift]})
60
+ for i, game in enumerate(games, 1):
61
+ game["id"] = f"{game['size']:02d}-{game['map_index']:03d}-{game['group']:02d}-{i:05d}"
62
+ return games
63
+
64
+
65
+ def audit(games, players):
66
+ """按人数统计出场、座位、交手是否均衡。"""
67
+ report = {}
68
+ for k in sorted({g["size"] for g in games}):
69
+ rows = [g for g in games if g["size"] == k]
70
+ appear = Counter(p for g in rows for p in g["seats"])
71
+ seat = Counter((p, i) for g in rows for i, p in enumerate(g["seats"]))
72
+ meet = Counter(frozenset(pair) for g in rows for pair in itertools.combinations(g["seats"], 2))
73
+ pairs = [meet[frozenset(pair)] for pair in itertools.combinations(players, 2)]
74
+ report[k] = {"games": len(rows), "maps": len({g["map_seed"] for g in rows}),
75
+ "appear_min": min(appear[p] for p in players), "appear_max": max(appear[p] for p in players),
76
+ "seat_min": min(seat[p, i] for p in players for i in range(k) if appear[p]),
77
+ "seat_max": max(seat.values()),
78
+ "meet_min": min(pairs), "meet_max": max(pairs)}
79
+ return report
@@ -0,0 +1,183 @@
1
+ """正式赛:定赛程 → 跑比赛(可中断续跑)→ 出成绩。
2
+
3
+ python tournament.py plan 赛事配置.json 赛事目录
4
+ python tournament.py run 赛事目录 --workers 4
5
+ python tournament.py rate 赛事目录
6
+
7
+ 赛事配置(JSON):
8
+ game 游戏引擎文件(提供 Game 类)
9
+ anchor 基准的名字,实力值以它为 0
10
+ players {名字: 程序路径}
11
+ sizes 要打的单局人数列表
12
+ maps_per_size 每种人数用几张地图
13
+ seed 赛程随机种子(可选;不填则随机,写入赛程后冻结)
14
+ replays 是否保存全部回放(默认 true,gzip 压缩)
15
+ build_dir 编译结果放在哪里(可选;默认是赛事目录下的 build/)
16
+ copies 每位选手(含基准)复制几份上场(可选,默认 1)。单局人数超过选手数时用,
17
+ 复制品名为 名字#2、名字#3……,计分时合并,复制品之间不比较
18
+
19
+ rate 可以一次读多个赛事目录合并计分,比如 2–6 方和补跑的 7–15 方:
20
+ python tournament.py rate 赛事目录A 赛事目录B
21
+ 成绩写在第一个目录里。
22
+ """
23
+ from __future__ import annotations
24
+
25
+ import argparse
26
+ import gzip
27
+ import importlib.util
28
+ import json
29
+ import os
30
+ import secrets
31
+ import sys
32
+ import time
33
+ from concurrent.futures import ProcessPoolExecutor, as_completed
34
+ from pathlib import Path
35
+
36
+ HERE = Path(__file__).resolve().parent
37
+ sys.path.insert(0, str(HERE))
38
+ import bt # noqa: E402
39
+ import core # noqa: E402
40
+ import schedule # noqa: E402
41
+
42
+ _GAMES = {}
43
+
44
+
45
+ def load_game(path):
46
+ if path not in _GAMES:
47
+ spec = importlib.util.spec_from_file_location(f"game_{len(_GAMES)}", path)
48
+ module = importlib.util.module_from_spec(spec)
49
+ sys.path.insert(0, str(Path(path).parent))
50
+ spec.loader.exec_module(module)
51
+ _GAMES[path] = module.Game
52
+ return _GAMES[path]
53
+
54
+
55
+ def plan(config_path, out):
56
+ out = Path(out)
57
+ if (out / "赛程.json").exists():
58
+ raise SystemExit("赛程已存在,不覆盖。要重排请换一个赛事目录。")
59
+ cfg = json.loads(Path(config_path).read_text(encoding="utf-8-sig")) # 兼容记事本存的带 BOM 文件
60
+ base = Path(config_path).resolve().parent
61
+ players = {n: str((base / p).resolve()) for n, p in cfg["players"].items()}
62
+ copies = cfg.get("copies", 1) # 人数超过选手数时,每位选手复制几份上场
63
+ players.update({f"{n}#{k}": p for n, p in list(players.items()) for k in range(2, copies + 1)})
64
+ for n, p in players.items():
65
+ if not Path(p).is_file():
66
+ raise SystemExit(f"{n} 的程序不存在:{p}")
67
+ seed = cfg.get("seed", secrets.randbits(48))
68
+ games = schedule.make_schedule(sorted(players), cfg["sizes"], cfg["maps_per_size"], seed)
69
+ doc = {"game": str((base / cfg["game"]).resolve()), "anchor": cfg["anchor"], "players": players,
70
+ "sizes": cfg["sizes"], "maps_per_size": cfg["maps_per_size"], "seed": seed,
71
+ "replays": cfg.get("replays", True), "build_dir": cfg.get("build_dir"), "audit": schedule.audit(games, sorted(players)), "games": games}
72
+ out.mkdir(parents=True, exist_ok=True)
73
+ (out / "赛程.json").write_text(json.dumps(doc, ensure_ascii=False, indent=1), encoding="utf-8")
74
+ total = len(games)
75
+ print(f"已冻结赛程:{total} 局")
76
+ for k, a in doc["audit"].items():
77
+ print(f" {k} 人局 {a['games']} 局,每人出场 {a['appear_min']}–{a['appear_max']},两两交手 {a['meet_min']}–{a['meet_max']}")
78
+
79
+
80
+ def play(game_path, commands, g, replay_dir):
81
+ Game = load_game(game_path)
82
+ t = time.time()
83
+ result, replay = core.run_match(Game, [commands[n] for n in g["seats"]], g["seats"], g["map_seed"],
84
+ record=replay_dir is not None)
85
+ if replay is not None:
86
+ path = Path(replay_dir) / f"{g['id']}.json.gz"
87
+ path.parent.mkdir(parents=True, exist_ok=True)
88
+ with gzip.open(path, "wt", encoding="utf-8") as f:
89
+ json.dump(replay, f, ensure_ascii=False, separators=(",", ":"))
90
+ return {"id": g["id"], "size": g["size"], "map_seed": g["map_seed"], "seats": g["seats"],
91
+ "score": result["score"], "turns": result.get("turns"), "seconds": round(time.time() - t, 2),
92
+ "faults": {b["name"]: b["dead"] or len(b["faults"]) for b in result["bots"] if b["faults"] or b["dead"]},
93
+ "max_ms": {b["name"]: b["max_ms"] for b in result["bots"]},
94
+ "stats": result.get("stats")}
95
+
96
+
97
+ def run(out, workers):
98
+ out = Path(out)
99
+ doc = json.loads((out / "赛程.json").read_text(encoding="utf-8"))
100
+ done_file = out / "结果.jsonl"
101
+ done = set()
102
+ if done_file.exists():
103
+ for line in done_file.read_text(encoding="utf-8").splitlines():
104
+ if line.strip():
105
+ done.add(json.loads(line)["id"])
106
+ todo = [g for g in doc["games"] if g["id"] not in done]
107
+ print(f"共 {len(doc['games'])} 局,已完成 {len(done)},本次运行 {len(todo)},并发 {workers}", flush=True)
108
+ replay_dir = str(out / "回放") if doc["replays"] else None
109
+ # 开赛前在主进程里统一编译一次
110
+ build = Path(doc.get("build_dir") or out / "build")
111
+ commands = {}
112
+ for name, path in doc["players"].items():
113
+ try:
114
+ commands[name] = core.command_for(path, build)
115
+ except RuntimeError as exc:
116
+ raise SystemExit(f"{name}:{exc}")
117
+ print(f"已编译 {len(commands)} 个程序,放在 {build}", flush=True)
118
+ start = time.time()
119
+ with ProcessPoolExecutor(workers) as pool, done_file.open("a", encoding="utf-8") as f:
120
+ futures = [pool.submit(play, doc["game"], commands, g, replay_dir) for g in todo]
121
+ for i, fut in enumerate(as_completed(futures), 1):
122
+ row = fut.result()
123
+ f.write(json.dumps(row, ensure_ascii=False) + "\n")
124
+ f.flush()
125
+ if i % 25 == 0 or i == len(todo):
126
+ rate_ = i / (time.time() - start)
127
+ print(f" {len(done) + i}/{len(doc['games'])} 约剩 {(len(todo) - i) / rate_ / 60:.1f} 分钟", flush=True)
128
+
129
+
130
+ def rate(outs):
131
+ outs = [Path(o) for o in outs]
132
+ out = outs[0]
133
+ rows, names, anchor = [], set(), None
134
+ for o in outs:
135
+ doc = json.loads((o / "赛程.json").read_text(encoding="utf-8"))
136
+ part = [json.loads(x) for x in (o / "结果.jsonl").read_text(encoding="utf-8").splitlines() if x.strip()]
137
+ if len(part) < len(doc["games"]):
138
+ print(f"注意:{o.name} 只完成了 {len(part)}/{len(doc['games'])} 局,以下为中途成绩。")
139
+ if anchor not in (None, doc["anchor"]):
140
+ raise SystemExit("合并计分的几个赛事目录,基准必须相同")
141
+ anchor = doc["anchor"]
142
+ rows += part
143
+ names |= {bt.base(n) for n in doc["players"]}
144
+ names = sorted(names)
145
+ doc = {"anchor": anchor}
146
+ ci, per_size = bt.rate(rows, names, doc["anchor"])
147
+ order = sorted(names, key=lambda n: -ci[n][0])
148
+ sizes = sorted(per_size)
149
+ lines = ["| 名次 | 选手 | 实力值 | 95% 区间 | " + " | ".join(f"{k}人" for k in sizes) + " | 故障局 |",
150
+ "|---:|---|---:|---|" + "---:|" * len(sizes) + "---:|"]
151
+ faults = {n: sum(1 for r in rows if any(bt.base(f) == n for f in r["faults"])) for n in names}
152
+ rank = 0
153
+ for n in order:
154
+ if n != doc["anchor"]:
155
+ rank += 1
156
+ v, lo, hi = ci[n]
157
+ lines.append(f"| {'—' if n == doc['anchor'] else rank} | {n} | {v:.0f} | {lo:.0f} ~ {hi:.0f} | " +
158
+ " | ".join(f"{per_size[k][n]:.0f}" for k in sizes) + f" | {faults[n]} |")
159
+ text = (f"# 成绩\n\n{len(rows)} 局。实力值以{doc['anchor']}为 0,强 10 倍记 +400;各人数等权;"
160
+ f"区间按地图重抽样 200 次。\n\n" + "\n".join(lines) + "\n")
161
+ (out / "成绩.md").write_text(text, encoding="utf-8")
162
+ (out / "成绩.json").write_text(json.dumps({"ratings": ci, "per_size": per_size}, ensure_ascii=False, indent=1),
163
+ encoding="utf-8")
164
+ print(text)
165
+
166
+
167
+ def main():
168
+ ap = argparse.ArgumentParser()
169
+ sub = ap.add_subparsers(dest="cmd", required=True)
170
+ p = sub.add_parser("plan"); p.add_argument("config"); p.add_argument("out")
171
+ p = sub.add_parser("run"); p.add_argument("out"); p.add_argument("--workers", type=int, default=4)
172
+ p = sub.add_parser("rate"); p.add_argument("out", nargs="+")
173
+ a = ap.parse_args()
174
+ if a.cmd == "plan":
175
+ plan(a.config, a.out)
176
+ elif a.cmd == "run":
177
+ run(a.out, a.workers)
178
+ else:
179
+ rate(a.out)
180
+
181
+
182
+ if __name__ == "__main__":
183
+ main()
@@ -0,0 +1,16 @@
1
+ # 各模型的交卷代码
2
+
3
+ 都是模型在比赛中独立写出、交卷后封存的原样代码,主办方没有改动。基准是 `../workspace/baseline.cpp`(选手包里公开的,可修改的练习对手)。
4
+
5
+ | 文件 | 模型 | 工具 | 正式赛名次 | BT 实力值 | SHA-256(前 16 位) |
6
+ |---|---|---|---:|---:|---|
7
+ | `gpt-6-astra.cpp` | GPT-6 Astra | Codex | 1 | 488.6 | `ffa1355ef2a13874` |
8
+ | `opus-5.5.cpp` | Opus 5.5 | Claude Code | 2 | 321.8 | `5bb8cbb8c99f8d56` |
9
+ | `gpt-6-sol.cpp` | GPT-6 Sol | Codex | 3 | 301.9 | `6cf1e62beeb04a78` |
10
+ | `deepseek-v4.1-flash.cpp` | DeepSeek V4.1 Flash | ZCode | 4 | 270.7 | `893ca913a06e3a36` |
11
+ | `kimi-k3.cpp` | Kimi K3 | ZCode | 5 | 230.4 | `204e89bcc828cd0a` |
12
+ | `qwen-3.8-max.cpp` | Qwen 3.8 Max | ZCode | 6 | 226.5 | `37fa03b4f491362d` |
13
+ | `glm-5.3.cpp` | GLM 5.3 | ZCode | 7 | 175.0 | `62cfd8c25179af31` |
14
+ | `doubao-seed-2.1.cpp` | 豆包 Seed 2.1 | ZCode | 8 | 95.4 | `230cea2edfdaaf24` |
15
+ | `mimo-v2.6-pro.cpp` | MiMo V2.6 Pro | ZCode | 9 | 39.4 | `00cc917e49b93b59` |
16
+ | `../workspace/baseline.cpp` | 基准 | — | — | 0 | `a4ca3e21d2f8a75c` |