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,619 @@
1
+ // Territory bot: loop planner with enemy-arrival safety model + opportunistic trail cutting.
2
+ #include <bits/stdc++.h>
3
+ using namespace std;
4
+
5
+ #ifndef DBG_T0
6
+ #define DBG_T0 250
7
+ #endif
8
+ #ifndef DBG_T1
9
+ #define DBG_T1 300
10
+ #endif
11
+ #ifndef P_EWM
12
+ #define P_EWM 2.5
13
+ #endif
14
+ #ifndef P_EW2
15
+ #define P_EW2 2.5
16
+ #endif
17
+ #ifndef P_MARGIN
18
+ #define P_MARGIN 0
19
+ #endif
20
+ #ifndef P_C0
21
+ #define P_C0 20.0
22
+ #endif
23
+ #ifndef P_KB
24
+ #define P_KB 6.0
25
+ #endif
26
+ #ifndef P_KW
27
+ #define P_KW 1.5
28
+ #endif
29
+ #ifndef P_BUDGET_MS
30
+ #define P_BUDGET_MS 8.0
31
+ #endif
32
+ #ifndef P_RAND_ITERS
33
+ #define P_RAND_ITERS 3000
34
+ #endif
35
+ #ifndef P_RAND_MS
36
+ #define P_RAND_MS 5.5
37
+ #endif
38
+ #ifndef P_TOPK
39
+ #define P_TOPK 6000
40
+ #endif
41
+
42
+ const int DX[4] = {0, 0, -1, 1};
43
+ const int DY[4] = {-1, 1, 0, 0};
44
+ const char DC[4] = {'U', 'D', 'L', 'R'};
45
+ const int INF = 1e9;
46
+ const int MAXC = 48 * 48;
47
+
48
+ int W, H, MAX_TURNS, N, ME, MOVE_MS, INIT_MS;
49
+ int spx[4], spy[4];
50
+ int own[MAXC], trl[MAXC], prot[MAXC];
51
+ struct Player { int x, y, dir, tl, area, deaths; } pl[4];
52
+ int turnNo;
53
+ int fbCount = 0, outTurns = 0;
54
+ int deE[4][MAXC], deMin[MAXC], Te[4];
55
+ int hd[MAXC]; // distance to own territory
56
+ int ps[49 * 49]; // prefix sums of own==ME
57
+ vector<int> myTrail;
58
+ int minDeEx;
59
+ double EW;
60
+ int committed = INF; // remaining length of the plan chosen last turn (after that move)
61
+ struct Leg { int d, len; }; // len<0: until captured
62
+ Leg planLegs[6]; int planNl = 0; int planExpX = -1, planExpY = -1, planDeaths = 0;
63
+
64
+ int dirIndex(char c) { return string("UDLR").find(c); }
65
+ inline bool inb(int x, int y) { return x >= 0 && x < W && y >= 0 && y < H; }
66
+
67
+ chrono::steady_clock::time_point t0;
68
+ double elapsedMs() { return chrono::duration<double, milli>(chrono::steady_clock::now() - t0).count(); }
69
+
70
+ bool readState() {
71
+ string tok;
72
+ if (!(cin >> tok)) return false;
73
+ cin >> turnNo;
74
+ for (int i = 0; i < N; i++) {
75
+ int id; char d; Player q;
76
+ cin >> tok >> id >> q.x >> q.y >> d >> q.tl >> q.area >> q.deaths;
77
+ q.dir = dirIndex(d);
78
+ pl[id] = q;
79
+ }
80
+ string row;
81
+ cin >> tok;
82
+ for (int y = 0; y < H; y++) {
83
+ cin >> row;
84
+ for (int x = 0; x < W; x++) own[y * W + x] = row[x] == '.' ? -1 : row[x] - '0';
85
+ }
86
+ cin >> tok;
87
+ for (int y = 0; y < H; y++) {
88
+ cin >> row;
89
+ for (int x = 0; x < W; x++) trl[y * W + x] = row[x] == '.' ? -1 : row[x] - '0';
90
+ }
91
+ cin >> tok;
92
+ return true;
93
+ }
94
+
95
+ static int qbuf[MAXC];
96
+ // BFS from (sx,sy) heading dir; cannot reverse on first step; cells with trl==blockOwner are blocked.
97
+ void bfsFrom(int sx, int sy, int dir, int blockOwner, int* dist) {
98
+ for (int i = 0; i < W * H; i++) dist[i] = INF;
99
+ int qh = 0, qt = 0;
100
+ int s = sy * W + sx;
101
+ dist[s] = 0;
102
+ for (int d = 0; d < 4; d++) {
103
+ if (d == (dir ^ 1)) continue;
104
+ int nx = sx + DX[d], ny = sy + DY[d];
105
+ if (!inb(nx, ny)) continue;
106
+ int k = ny * W + nx;
107
+ if (trl[k] == blockOwner || dist[k] != INF) continue;
108
+ dist[k] = 1;
109
+ qbuf[qt++] = k;
110
+ }
111
+ while (qh < qt) {
112
+ int k = qbuf[qh++];
113
+ int x = k % W, y = k / W;
114
+ for (int d = 0; d < 4; d++) {
115
+ int nx = x + DX[d], ny = y + DY[d];
116
+ if (!inb(nx, ny)) continue;
117
+ int nk = ny * W + nx;
118
+ if (trl[nk] == blockOwner || dist[nk] != INF) continue;
119
+ dist[nk] = dist[k] + 1;
120
+ qbuf[qt++] = nk;
121
+ }
122
+ }
123
+ }
124
+
125
+ // ---------- path simulation ----------
126
+ struct Sim {
127
+ int n, L;
128
+ bool cap;
129
+ int cells[260];
130
+ int nt;
131
+ int tcells[260];
132
+ int minx, maxx, miny, maxy;
133
+ };
134
+ static int visStamp[MAXC];
135
+ static int stampCtr = 0;
136
+
137
+ bool simulate(const Leg* legs, int nl, Sim& s) {
138
+ stampCtr++;
139
+ int x = pl[ME].x, y = pl[ME].y;
140
+ bool hasTrail = pl[ME].tl > 0;
141
+ s.n = 0; s.nt = 0; s.cap = false;
142
+ s.minx = s.maxx = x; s.miny = s.maxy = y;
143
+ bool first = true;
144
+ for (int li = 0; li < nl; li++) {
145
+ int d = legs[li].d;
146
+ int len = legs[li].len;
147
+ int cnt = 0;
148
+ int lim = len < 0 ? 45 : len;
149
+ while (cnt < lim) {
150
+ if (first && d == (pl[ME].dir ^ 1)) return false;
151
+ first = false;
152
+ x += DX[d]; y += DY[d];
153
+ if (!inb(x, y)) return false;
154
+ int k = y * W + x;
155
+ if (trl[k] == ME || visStamp[k] == stampCtr) return false;
156
+ if (s.n >= 250) return false;
157
+ s.cells[s.n++] = k;
158
+ cnt++;
159
+ if (x < s.minx) s.minx = x;
160
+ if (x > s.maxx) s.maxx = x;
161
+ if (y < s.miny) s.miny = y;
162
+ if (y > s.maxy) s.maxy = y;
163
+ if (own[k] == ME) {
164
+ if (hasTrail) { s.cap = true; s.L = s.n; return true; }
165
+ } else {
166
+ visStamp[k] = stampCtr;
167
+ s.tcells[s.nt++] = k;
168
+ hasTrail = true;
169
+ }
170
+ }
171
+ }
172
+ s.L = s.n;
173
+ return false;
174
+ }
175
+
176
+ bool pathSafe(const Sim& s) {
177
+ int L = s.L + P_MARGIN;
178
+ if (pl[ME].tl > 0 && minDeEx <= L) return false;
179
+ for (int i = 0; i < s.nt; i++)
180
+ if (deMin[s.tcells[i]] <= L) return false;
181
+ return true;
182
+ }
183
+
184
+ typedef unsigned long long u64;
185
+ u64 rOwnMe[48], rTrlMe[48], rNeuNP[48], rEneNP[48], rFull;
186
+ double constGain; // value of my existing trail cells
187
+ inline double cellVal(int k) {
188
+ if (prot[k] != -1 && prot[k] != ME) return 0;
189
+ if (own[k] == ME) return 0;
190
+ return own[k] == -1 ? 1.0 : EW;
191
+ }
192
+ inline u64 hfill(u64 seed, u64 free) {
193
+ u64 g = seed, p = free;
194
+ g |= p & (g << 1); p &= p << 1;
195
+ g |= p & (g << 2); p &= p << 2;
196
+ g |= p & (g << 4); p &= p << 4;
197
+ g |= p & (g << 8); p &= p << 8;
198
+ g |= p & (g << 16); p &= p << 16;
199
+ g |= p & (g << 32);
200
+ return g;
201
+ }
202
+ inline u64 hfillR(u64 seed, u64 free) {
203
+ u64 g = seed, p = free;
204
+ g |= p & (g >> 1); p &= p >> 1;
205
+ g |= p & (g >> 2); p &= p >> 2;
206
+ g |= p & (g >> 4); p &= p >> 4;
207
+ g |= p & (g >> 8); p &= p >> 8;
208
+ g |= p & (g >> 16); p &= p >> 16;
209
+ g |= p & (g >> 32);
210
+ return g;
211
+ }
212
+ void buildRows() {
213
+ rFull = (W == 64) ? ~0ULL : ((1ULL << W) - 1);
214
+ for (int y = 0; y < H; y++) {
215
+ u64 o = 0, t = 0, n = 0, e = 0;
216
+ for (int x = 0; x < W; x++) {
217
+ int k = y * W + x;
218
+ if (own[k] == ME) o |= 1ULL << x;
219
+ if (trl[k] == ME) t |= 1ULL << x;
220
+ if (!(prot[k] != -1 && prot[k] != ME)) {
221
+ if (own[k] == -1) n |= 1ULL << x;
222
+ else if (own[k] != ME) e |= 1ULL << x;
223
+ }
224
+ }
225
+ rOwnMe[y] = o; rTrlMe[y] = t; rNeuNP[y] = n; rEneNP[y] = e;
226
+ }
227
+ constGain = 0;
228
+ for (int k : myTrail) constGain += cellVal(k);
229
+ }
230
+ // exact weighted gain of completing the sim'd loop (bit-parallel flood fill)
231
+ double exactGain(const Sim& s) {
232
+ u64 wall[48], reach[48];
233
+ for (int y = 0; y < H; y++) { wall[y] = rOwnMe[y] | rTrlMe[y]; reach[y] = 0; }
234
+ double g = constGain;
235
+ for (int i = 0; i < s.nt; i++) {
236
+ int k = s.tcells[i];
237
+ wall[k / W] |= 1ULL << (k % W);
238
+ g += cellVal(k);
239
+ }
240
+ u64 edge = 1ULL | (1ULL << (W - 1));
241
+ for (int y = 0; y < H; y++) {
242
+ u64 fr = ~wall[y] & rFull;
243
+ reach[y] = (y == 0 || y == H - 1) ? fr : (fr & edge);
244
+ }
245
+ bool ch = true;
246
+ while (ch) {
247
+ ch = false;
248
+ for (int y = 0; y < H; y++) {
249
+ u64 fr = ~wall[y] & rFull;
250
+ u64 sd = reach[y] | (fr & ((y > 0 ? reach[y - 1] : 0) | (y < H - 1 ? reach[y + 1] : 0)));
251
+ u64 nr = hfill(sd, fr) | hfillR(sd, fr);
252
+ if (nr != reach[y]) { reach[y] = nr; ch = true; }
253
+ }
254
+ for (int y = H - 1; y >= 0; y--) {
255
+ u64 fr = ~wall[y] & rFull;
256
+ u64 sd = reach[y] | (fr & ((y > 0 ? reach[y - 1] : 0) | (y < H - 1 ? reach[y + 1] : 0)));
257
+ u64 nr = hfill(sd, fr) | hfillR(sd, fr);
258
+ if (nr != reach[y]) { reach[y] = nr; ch = true; }
259
+ }
260
+ }
261
+ for (int y = 0; y < H; y++) {
262
+ u64 enc = ~wall[y] & rFull & ~reach[y];
263
+ if (enc) g += __builtin_popcountll(enc & rNeuNP[y]) + EW * __builtin_popcountll(enc & rEneNP[y]);
264
+ }
265
+ return g;
266
+ }
267
+
268
+ double estGain(const Sim& s) {
269
+ int x0 = s.minx, x1 = s.maxx, y0 = s.miny, y1 = s.maxy;
270
+ int area = (x1 - x0 + 1) * (y1 - y0 + 1);
271
+ int W1 = W + 1;
272
+ int mine = ps[(y1 + 1) * W1 + x1 + 1] - ps[y0 * W1 + x1 + 1] - ps[(y1 + 1) * W1 + x0] + ps[y0 * W1 + x0];
273
+ return area - mine;
274
+ }
275
+
276
+ struct Cand { double est; Leg legs[4]; int nl; };
277
+
278
+ // pick a move that stays inside own territory as long as possible
279
+ int stayInside() {
280
+ const Player& me = pl[ME];
281
+ int bestD = -1, bestScore = -1;
282
+ for (int d = 0; d < 4; d++) {
283
+ if (d == (me.dir ^ 1)) continue;
284
+ int nx = me.x + DX[d], ny = me.y + DY[d];
285
+ if (!inb(nx, ny) || trl[ny * W + nx] == ME) continue;
286
+ int score = 0;
287
+ if (own[ny * W + nx] == ME) {
288
+ score = 1;
289
+ // depth-limited lookahead on staying inside
290
+ function<int(int, int, int, int)> go = [&](int x, int y, int dir, int depth) -> int {
291
+ if (depth == 0) return 0;
292
+ int best = 0;
293
+ for (int e = 0; e < 4; e++) {
294
+ if (e == (dir ^ 1)) continue;
295
+ int ax = x + DX[e], ay = y + DY[e];
296
+ if (!inb(ax, ay) || own[ay * W + ax] != ME) continue;
297
+ best = max(best, 1 + go(ax, ay, e, depth - 1));
298
+ }
299
+ return best;
300
+ };
301
+ score += go(nx, ny, d, 6);
302
+ }
303
+ if (score > bestScore) { bestScore = score; bestD = d; }
304
+ }
305
+ return bestD;
306
+ }
307
+
308
+ int decide() {
309
+ t0 = chrono::steady_clock::now();
310
+ const Player& me = pl[ME];
311
+ int WH = W * H;
312
+ int remaining = MAX_TURNS - turnNo;
313
+
314
+ // prefix sums
315
+ int W1 = W + 1;
316
+ for (int y = 0; y < H; y++)
317
+ for (int x = 0; x < W; x++)
318
+ ps[(y + 1) * W1 + x + 1] = ps[y * W1 + x + 1] + ps[(y + 1) * W1 + x] - ps[y * W1 + x] + (own[y * W + x] == ME);
319
+ myTrail.clear();
320
+ for (int k = 0; k < WH; k++) if (trl[k] == ME) myTrail.push_back(k);
321
+ buildRows();
322
+
323
+ // enemy distance maps
324
+ for (int k = 0; k < WH; k++) deMin[k] = INF;
325
+ for (int e = 0; e < N; e++) {
326
+ if (e == ME) continue;
327
+ bfsFrom(pl[e].x, pl[e].y, pl[e].dir, e, deE[e]);
328
+ for (int k = 0; k < WH; k++) deMin[k] = min(deMin[k], deE[e][k]);
329
+ int t = INF;
330
+ for (int k = 0; k < WH; k++) if (own[k] == e) t = min(t, deE[e][k]);
331
+ Te[e] = (own[pl[e].y * W + pl[e].x] == e) ? 0 : t;
332
+ }
333
+ minDeEx = INF;
334
+ for (int k : myTrail) minDeEx = min(minDeEx, deMin[k]);
335
+ { // distance to own territory, avoiding my trail
336
+ int qh = 0, qt = 0;
337
+ for (int k = 0; k < WH; k++) { hd[k] = INF; if (own[k] == ME) { hd[k] = 0; qbuf[qt++] = k; } }
338
+ while (qh < qt) {
339
+ int k = qbuf[qh++];
340
+ int x = k % W, y = k / W;
341
+ for (int d = 0; d < 4; d++) {
342
+ int nx = x + DX[d], ny = y + DY[d];
343
+ if (!inb(nx, ny)) continue;
344
+ int nk = ny * W + nx;
345
+ if (trl[nk] == ME || hd[nk] != INF) continue;
346
+ hd[nk] = hd[k] + 1;
347
+ qbuf[qt++] = nk;
348
+ }
349
+ }
350
+ }
351
+ // commitment: while outside, the plan may never get longer than what is left of the last one
352
+ if (me.tl == 0 || me.deaths != planDeaths) { committed = INF; planNl = 0; }
353
+ planDeaths = me.deaths;
354
+ int limitL = (me.tl > 0) ? committed - 1 : INF;
355
+ #ifndef USE_LIMIT
356
+ limitL = INF;
357
+ #endif
358
+ if (limitL > remaining) limitL = remaining;
359
+
360
+ // ---- loop candidates ----
361
+ vector<Cand> cands;
362
+ cands.reserve(4000);
363
+ Sim s;
364
+ int A = 26, B = 20;
365
+ for (int d1 = 0; d1 < 4; d1++) {
366
+ if (d1 == (me.dir ^ 1)) continue;
367
+ for (int a = 1; a <= A; a++) {
368
+ for (int si = 0; si < 2; si++) {
369
+ int sd = (d1 < 2 ? 2 : 0) + si;
370
+ // 2-leg
371
+ {
372
+ Cand c; c.nl = 2;
373
+ c.legs[0] = {d1, a}; c.legs[1] = {sd, -1};
374
+ if (simulate(c.legs, c.nl, s) && s.cap && s.L <= limitL && pathSafe(s)) {
375
+ c.est = estGain(s) / (s.L + P_C0);
376
+ cands.push_back(c);
377
+ }
378
+ }
379
+ for (int b = 1; b <= B; b++) {
380
+ Cand c; c.nl = 3;
381
+ c.legs[0] = {d1, a}; c.legs[1] = {sd, b}; c.legs[2] = {d1 ^ 1, -1};
382
+ if (simulate(c.legs, c.nl, s) && s.cap && s.L <= limitL && pathSafe(s)) {
383
+ c.est = estGain(s) / (s.L + P_C0);
384
+ cands.push_back(c);
385
+ }
386
+ }
387
+ }
388
+ }
389
+ }
390
+ int K = min<int>(P_TOPK, cands.size());
391
+ if (K > 0) {
392
+ partial_sort(cands.begin(), cands.begin() + K, cands.end(), [](const Cand& a, const Cand& b) { return a.est > b.est; });
393
+ }
394
+ double bestRate = 0;
395
+ int bestDir = -1;
396
+ Leg bestLegs[6]; int bestNl = 0;
397
+ if (planNl > 0 && me.x == planExpX && me.y == planExpY && me.tl > 0) {
398
+ Leg lg[6]; int nl = 0;
399
+ for (int j = 0; j < planNl; j++) lg[nl++] = planLegs[j];
400
+ if (lg[0].len > 0) { lg[0].len--; if (lg[0].len == 0) { for (int j = 1; j < nl; j++) lg[j - 1] = lg[j]; nl--; } }
401
+ if (nl > 0 && simulate(lg, nl, s) && s.cap && s.L <= limitL && pathSafe(s)) {
402
+ double g = exactGain(s);
403
+ double rate = g / (s.L + P_C0);
404
+ bestRate = rate; bestDir = lg[0].d; bestNl = nl; for (int j = 0; j < nl; j++) bestLegs[j] = lg[j];
405
+ bestRate = rate * 1.02; // slight stickiness
406
+ }
407
+ }
408
+ for (int i = 0; i < K; i++) {
409
+ if (elapsedMs() > P_BUDGET_MS) break;
410
+ Cand& c = cands[i];
411
+ if (!simulate(c.legs, c.nl, s)) continue;
412
+ double g = exactGain(s);
413
+ double rate = g / (s.L + P_C0);
414
+ if (rate > bestRate) {
415
+ bestRate = rate; bestDir = c.legs[0].d;
416
+ bestNl = c.nl; for (int j = 0; j < c.nl; j++) bestLegs[j] = c.legs[j];
417
+ }
418
+ }
419
+ // random / hill-climbing refinement with the remaining budget
420
+ {
421
+ static uint64_t rs = 88172645463325252ULL;
422
+ auto rnd = [&]() { rs ^= rs << 13; rs ^= rs >> 7; rs ^= rs << 17; return (unsigned)(rs >> 11); };
423
+ int iters = 0;
424
+ while (iters < P_RAND_ITERS && elapsedMs() < P_RAND_MS) {
425
+ iters++;
426
+ Leg lg[6]; int nl;
427
+ if (bestNl > 0 && (rnd() & 1)) {
428
+ nl = bestNl;
429
+ for (int j = 0; j < nl; j++) lg[j] = bestLegs[j];
430
+ int m = rnd() % nl;
431
+ if (lg[m].len < 0) m = (nl >= 2) ? nl - 2 : 0;
432
+ if (lg[m].len < 0) continue;
433
+ int delta = (int)(rnd() % 7) - 3;
434
+ if (delta == 0) delta = 1;
435
+ lg[m].len += delta;
436
+ if (lg[m].len < 1) continue;
437
+ if (rnd() % 3 == 0 && nl >= 2) { // second mutation
438
+ int m2 = rnd() % nl;
439
+ if (lg[m2].len > 0) { lg[m2].len += (int)(rnd() % 5) - 2; if (lg[m2].len < 1) continue; }
440
+ }
441
+ } else {
442
+ nl = 3 + rnd() % 3;
443
+ int d = rnd() % 4;
444
+ if (d == (me.dir ^ 1)) d = me.dir;
445
+ lg[0] = {d, 1 + (int)(rnd() % 22)};
446
+ for (int j = 1; j < nl; j++) {
447
+ int pd = lg[j - 1].d;
448
+ int nd = ((pd < 2) ? 2 : 0) + (rnd() & 1);
449
+ lg[j] = {nd, j == nl - 1 ? -1 : 1 + (int)(rnd() % 14)};
450
+ }
451
+ }
452
+ if (!simulate(lg, nl, s) || !s.cap || s.L > limitL || !pathSafe(s)) continue;
453
+ double g = exactGain(s);
454
+ double rate = g / (s.L + P_C0);
455
+ if (rate > bestRate) {
456
+ bestRate = rate; bestDir = lg[0].d;
457
+ bestNl = nl; for (int j = 0; j < nl; j++) bestLegs[j] = lg[j];
458
+ }
459
+ }
460
+ }
461
+
462
+ int bestL = 0;
463
+ if (bestDir >= 0) { if (simulate(bestLegs, bestNl, s)) bestL = s.L; else bestL = INF; }
464
+ // ---- attack candidates ----
465
+ {
466
+ static int dm[MAXC], par[MAXC];
467
+ // BFS from my head, blocked by my own trail; keep parent
468
+ for (int k = 0; k < WH; k++) dm[k] = INF;
469
+ int qh = 0, qt = 0;
470
+ int st = me.y * W + me.x;
471
+ dm[st] = 0;
472
+ for (int d = 0; d < 4; d++) {
473
+ if (d == (me.dir ^ 1)) continue;
474
+ int nx = me.x + DX[d], ny = me.y + DY[d];
475
+ if (!inb(nx, ny)) continue;
476
+ int k = ny * W + nx;
477
+ if (trl[k] == ME || dm[k] != INF) continue;
478
+ dm[k] = 1; par[k] = d; qbuf[qt++] = k;
479
+ }
480
+ while (qh < qt) {
481
+ int k = qbuf[qh++];
482
+ int x = k % W, y = k / W;
483
+ for (int d = 0; d < 4; d++) {
484
+ int nx = x + DX[d], ny = y + DY[d];
485
+ if (!inb(nx, ny)) continue;
486
+ int nk = ny * W + nx;
487
+ if (trl[nk] == ME || dm[nk] != INF) continue;
488
+ dm[nk] = dm[k] + 1; par[nk] = d; qbuf[qt++] = nk;
489
+ }
490
+ }
491
+ for (int e = 0; e < N; e++) {
492
+ if (e == ME || pl[e].tl == 0) continue;
493
+ int bk = -1, bd = INF;
494
+ for (int k = 0; k < WH; k++)
495
+ if (trl[k] == e && dm[k] < bd) { bd = dm[k]; bk = k; }
496
+ if (bk < 0 || bd > Te[e]) continue;
497
+ int T = bd + (hd[bk] >= INF ? 1000 : hd[bk]);
498
+ if (T > limitL) continue;
499
+ int dirsBuf[128], n = 0;
500
+ int c = bk;
501
+ while (c != st && n < 120) {
502
+ int d = par[c];
503
+ dirsBuf[n++] = d;
504
+ c = (c % W - DX[d]) + (c / W - DY[d]) * W;
505
+ }
506
+ reverse(dirsBuf, dirsBuf + n);
507
+ bool safe = true;
508
+ int x = me.x, y = me.y;
509
+ for (int i = 0; i < n && safe; i++) {
510
+ x += DX[dirsBuf[i]]; y += DY[dirsBuf[i]];
511
+ int k = y * W + x;
512
+ if (i == n - 1) break;
513
+ if (own[k] == ME) continue;
514
+ if (deE[e][k] <= bd) safe = false;
515
+ for (int o = 0; o < N && safe; o++) {
516
+ if (o == ME || o == e) continue;
517
+ if (deE[o][k] <= T) safe = false;
518
+ }
519
+ }
520
+ if (safe && me.tl > 0) {
521
+ for (int k : myTrail) {
522
+ if (deE[e][k] <= bd) { safe = false; break; }
523
+ for (int o = 0; o < N && safe; o++)
524
+ if (o != ME && o != e && deE[o][k] <= T) safe = false;
525
+ if (!safe) break;
526
+ }
527
+ }
528
+ if (!safe) continue;
529
+ double V = P_KB + P_KW * pl[e].tl;
530
+ double rate = V / (bd + 2.0);
531
+ if (rate > bestRate) { bestRate = rate; bestDir = dirsBuf[0]; bestNl = 0; bestL = T; }
532
+ }
533
+ }
534
+
535
+ #ifdef DEBUG
536
+ if (turnNo >= DBG_T0 && turnNo < DBG_T1) cerr << "E" << pl[1-ME].x << ',' << pl[1-ME].y << 'd' << pl[1-ME].dir << " etl" << pl[1-ME].tl << " " << "t" << turnNo << " tl" << me.tl << " pos " << me.x << "," << me.y << " best " << bestDir << " rate " << bestRate << " nl " << bestNl << " cands " << cands.size() << " minDeEx " << minDeEx << " limitL " << limitL << " bestL " << bestL << endl;
537
+ #endif
538
+ if (bestDir >= 0) {
539
+ if (bestNl > 0) { planNl = bestNl; for (int j = 0; j < bestNl; j++) planLegs[j] = bestLegs[j]; }
540
+ else planNl = 0;
541
+ committed = bestL;
542
+ planExpX = me.x + DX[bestDir]; planExpY = me.y + DY[bestDir];
543
+ return bestDir;
544
+ }
545
+ planNl = 0;
546
+
547
+ // ---- fallbacks ----
548
+ fbCount++;
549
+ #ifdef DEBUG
550
+ if (turnNo % 5 == 0 && me.tl == 0) cerr << "FB t" << turnNo << " tl " << me.tl << " pos " << me.x << ',' << me.y << " cands " << cands.size() << " limitL " << limitL << endl;
551
+ #endif
552
+ if (me.tl == 0) {
553
+ int d = stayInside();
554
+ if (d >= 0) return d;
555
+ }
556
+ // outside or no inside move: go home by shortest path
557
+ {
558
+ static int dist[MAXC];
559
+ // BFS backwards from own territory: distance to nearest own cell avoiding my trail
560
+ for (int k = 0; k < WH; k++) dist[k] = INF;
561
+ int qh = 0, qt = 0;
562
+ for (int k = 0; k < WH; k++) if (own[k] == ME) { dist[k] = 0; qbuf[qt++] = k; }
563
+ while (qh < qt) {
564
+ int k = qbuf[qh++];
565
+ int x = k % W, y = k / W;
566
+ for (int d = 0; d < 4; d++) {
567
+ int nx = x + DX[d], ny = y + DY[d];
568
+ if (!inb(nx, ny)) continue;
569
+ int nk = ny * W + nx;
570
+ if (trl[nk] == ME || dist[nk] != INF) continue;
571
+ dist[nk] = dist[k] + 1;
572
+ qbuf[qt++] = nk;
573
+ }
574
+ }
575
+ int bd = -1, bv = INF;
576
+ for (int d = 0; d < 4; d++) {
577
+ if (d == (me.dir ^ 1)) continue;
578
+ int nx = me.x + DX[d], ny = me.y + DY[d];
579
+ if (!inb(nx, ny) || trl[ny * W + nx] == ME) continue;
580
+ int v = dist[ny * W + nx];
581
+ if (v < bv) { bv = v; bd = d; }
582
+ }
583
+ if (bd >= 0) return bd;
584
+ }
585
+ for (int d = 0; d < 4; d++) {
586
+ if (d == (me.dir ^ 1)) continue;
587
+ int nx = me.x + DX[d], ny = me.y + DY[d];
588
+ if (inb(nx, ny)) return d;
589
+ }
590
+ return me.dir;
591
+ }
592
+
593
+ int main() {
594
+ ios::sync_with_stdio(false);
595
+ cin.tie(nullptr);
596
+ string tok;
597
+ cin >> tok;
598
+ cin >> W >> H >> MAX_TURNS >> N >> ME >> MOVE_MS >> INIT_MS;
599
+ for (int i = 0; i < N; i++) {
600
+ int id, x, y;
601
+ cin >> tok >> id >> x >> y;
602
+ spx[id] = x; spy[id] = y;
603
+ }
604
+ for (int k = 0; k < W * H; k++) prot[k] = -1;
605
+ for (int i = 0; i < N; i++)
606
+ for (int y = spy[i] - 1; y <= spy[i] + 1; y++)
607
+ for (int x = spx[i] - 1; x <= spx[i] + 1; x++)
608
+ if (inb(x, y)) prot[y * W + x] = i;
609
+ EW = (N == 2) ? P_EW2 : P_EWM;
610
+ while (readState()) {
611
+ int d = decide();
612
+ #ifdef DEBUG
613
+ if (pl[ME].tl > 0) outTurns++;
614
+ if (turnNo % 50 == 0 || (turnNo>=280 && turnNo<=288)) { cerr << "t" << turnNo; for (int i = 0; i < N; i++) cerr << " [" << pl[i].area << " d" << pl[i].deaths << "]"; cerr << endl; }
615
+ if (turnNo == MAX_TURNS - 1) cerr << "fallback " << fbCount << " outside " << outTurns << " deaths " << pl[ME].deaths << " area " << pl[ME].area << endl;
616
+ #endif
617
+ cout << turnNo << ' ' << DC[d] << endl;
618
+ }
619
+ }
@@ -0,0 +1,17 @@
1
+ """把 replays/ 里的回放 JSON 嵌进查看器,生成双击即可观看的 replays/index.html。
2
+
3
+ python tournament/make_replay_page.py
4
+ """
5
+ import json
6
+ import sys
7
+ from pathlib import Path
8
+
9
+ sys.stdout.reconfigure(encoding="utf-8")
10
+ HERE = Path(__file__).resolve().parent.parent
11
+ viewer = (HERE / "workspace" / "viewer" / "viewer.html").read_text(encoding="utf-8")
12
+ items = [{"name": p.name, "data": json.loads(p.read_text(encoding="utf-8"))} for p in sorted((HERE / "replays").glob("*.json"))]
13
+ blob = json.dumps(items, ensure_ascii=False, separators=(",", ":")).replace("</", r"<\/")
14
+ boot = ("<script>\nconst EMBEDDED = " + blob + ";\n"
15
+ "load(EMBEDDED.map(r => new File([JSON.stringify(r.data)], r.name, {type: 'application/json'})));\n</script>\n</body>")
16
+ (HERE / "replays" / "index.html").write_text(viewer.replace("</body>", boot, 1), encoding="utf-8")
17
+ print(f"已生成 replays/index.html({len(items)} 局)")