dsh-eval-guard 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (224) hide show
  1. package/README.md +91 -13
  2. package/assets/bomberman/workspace.zip +0 -0
  3. package/assets/commit0/instance.json +30 -0
  4. package/assets/commit0/workspace.zip +0 -0
  5. package/assets/ctf/workspace.zip +0 -0
  6. package/assets/featurebench/hidden/test_regression.py +690 -0
  7. package/assets/featurebench/instance.json +16 -0
  8. package/assets/featurebench/workspace.zip +0 -0
  9. package/assets/swe-bench-pro/hidden/test.patch +36 -0
  10. package/assets/swe-bench-pro/instance.json +10 -0
  11. package/assets/swe-bench-pro/workspace.zip +0 -0
  12. package/assets/tank/workspace.zip +0 -0
  13. package/eval/README.md +18 -0
  14. package/eval/arena/arena/README.md +21 -0
  15. package/eval/arena/arena/bt.py +104 -0
  16. package/eval/arena/arena/clock.py +15 -0
  17. package/eval/arena/arena/core.py +265 -0
  18. package/eval/arena/arena/run.cmd +24 -0
  19. package/eval/arena/arena/run.sh +11 -0
  20. package/eval/arena/arena/schedule.py +79 -0
  21. package/eval/arena/arena/tournament.py +183 -0
  22. package/eval/arena/bomberman/bots/README.md +16 -0
  23. package/eval/arena/bomberman/bots/deepseek-v4.1-flash.cpp +447 -0
  24. package/eval/arena/bomberman/bots/doubao-seed-2.1.cpp +542 -0
  25. package/eval/arena/bomberman/bots/glm-5.3.cpp +406 -0
  26. package/eval/arena/bomberman/bots/gpt-6-astra.cpp +768 -0
  27. package/eval/arena/bomberman/bots/gpt-6-sol.cpp +444 -0
  28. package/eval/arena/bomberman/bots/kimi-k3.cpp +432 -0
  29. package/eval/arena/bomberman/bots/mimo-v2.6-pro.cpp +529 -0
  30. package/eval/arena/bomberman/bots/opus-5.5.cpp +548 -0
  31. package/eval/arena/bomberman/bots/qwen-3.8-max.cpp +836 -0
  32. package/eval/arena/bomberman/tournament/README.md +11 -0
  33. package/eval/arena/bomberman/tournament/bt.py +56 -0
  34. package/eval/arena/bomberman/tournament/evaluate.py +199 -0
  35. package/eval/arena/bomberman/tournament/run.py +79 -0
  36. package/eval/arena/bomberman/tournament/schedule-2to10.json.gz +0 -0
  37. package/eval/arena/bomberman/workspace/MANIFEST.json +13 -0
  38. package/eval/arena/bomberman/workspace/README.md +29 -0
  39. package/eval/arena/bomberman/workspace/RULES.md +100 -0
  40. package/eval/arena/bomberman/workspace/baseline.cpp +240 -0
  41. package/eval/arena/bomberman/workspace/clock.py +25 -0
  42. package/eval/arena/bomberman/workspace/engine/arena.py +198 -0
  43. package/eval/arena/bomberman/workspace/engine/build.py +35 -0
  44. package/eval/arena/bomberman/workspace/engine/engine.py +256 -0
  45. package/eval/arena/bomberman/workspace/engine/verify_replay.py +24 -0
  46. package/eval/arena/bomberman/workspace/run.cmd +28 -0
  47. package/eval/arena/bomberman/workspace/run.sh +16 -0
  48. package/eval/arena/bomberman/workspace/viewer/index.html +42 -0
  49. package/eval/arena/ctf/bots/README.md +18 -0
  50. package/eval/arena/ctf/bots/deepseek-v4.1-flash.cpp +601 -0
  51. package/eval/arena/ctf/bots/doubao-seed-2.1-turbo.cpp +320 -0
  52. package/eval/arena/ctf/bots/fable-5.1.cpp +634 -0
  53. package/eval/arena/ctf/bots/glm-5.3.cpp +689 -0
  54. package/eval/arena/ctf/bots/gpt-6-astra.cpp +657 -0
  55. package/eval/arena/ctf/bots/gpt-6.1-sol.cpp +725 -0
  56. package/eval/arena/ctf/bots/kimi-k3.cpp +667 -0
  57. package/eval/arena/ctf/bots/mimo-v2.6-pro.cpp +533 -0
  58. package/eval/arena/ctf/bots/opus-5.5.cpp +645 -0
  59. package/eval/arena/ctf/bots/qwen-3.8-max.cpp +726 -0
  60. package/eval/arena/ctf/bots/sonnet-5.5.cpp +569 -0
  61. package/eval/arena/ctf/tournament/README.md +15 -0
  62. package/eval/arena/ctf/tournament/formal-2to8.json +31 -0
  63. package/eval/arena/ctf/tournament/formal-9to15.json +31 -0
  64. package/eval/arena/ctf/workspace/README.md +41 -0
  65. package/eval/arena/ctf/workspace/RULES.md +119 -0
  66. package/eval/arena/ctf/workspace/arena.py +186 -0
  67. package/eval/arena/ctf/workspace/baseline.cpp +257 -0
  68. package/eval/arena/ctf/workspace/clock.py +15 -0
  69. package/eval/arena/ctf/workspace/core.py +265 -0
  70. package/eval/arena/ctf/workspace/engine.py +389 -0
  71. package/eval/arena/ctf/workspace/run.cmd +24 -0
  72. package/eval/arena/ctf/workspace/run.sh +11 -0
  73. package/eval/arena/ctf/workspace/viewer.html +71 -0
  74. package/eval/arena/land/bots/README.md +17 -0
  75. package/eval/arena/land/bots/deepseek-4.1f@workbuddy#1/bot.cpp +608 -0
  76. package/eval/arena/land/bots/glm-5.3@workbuddy#1/bot.cpp +521 -0
  77. package/eval/arena/land/bots/gpt-6-astra@codex#1/bot.cpp +373 -0
  78. package/eval/arena/land/bots/gpt-6-sol@codex#1/bot.cpp +341 -0
  79. package/eval/arena/land/bots/kimi-k3@workbuddy#1/bot.cpp +492 -0
  80. package/eval/arena/land/bots/mimo-2.6-pro@zcode#1/bot.cpp +652 -0
  81. package/eval/arena/land/bots/mystery-model@zcode#1/bot.cpp +466 -0
  82. package/eval/arena/land/bots/opus-5.5@claude-code#1/bot.cpp +630 -0
  83. package/eval/arena/land/bots/sonnet-5@claude-code#1/bot.cpp +619 -0
  84. package/eval/arena/land/tournament/make_replay_page.py +17 -0
  85. package/eval/arena/land/tournament/rate.py +290 -0
  86. package/eval/arena/land/tournament/round_robin.py +172 -0
  87. package/eval/arena/land/tournament/sparring.cpp +356 -0
  88. package/eval/arena/land/workspace/RULES.md +114 -0
  89. package/eval/arena/land/workspace/TASK.md +46 -0
  90. package/eval/arena/land/workspace/anchors/.build/greedy_f2e6d2dea5ca +0 -0
  91. package/eval/arena/land/workspace/anchors/greedy.cpp +194 -0
  92. package/eval/arena/land/workspace/anchors/random.cpp +80 -0
  93. package/eval/arena/land/workspace/clock.py +64 -0
  94. package/eval/arena/land/workspace/engine/build.py +33 -0
  95. package/eval/arena/land/workspace/engine/game.py +273 -0
  96. package/eval/arena/land/workspace/engine/referee.py +308 -0
  97. package/eval/arena/land/workspace/starter/.build/bot_be32b45b4699 +0 -0
  98. package/eval/arena/land/workspace/starter/bot.cpp +82 -0
  99. package/eval/arena/land/workspace/viewer/viewer.html +210 -0
  100. package/eval/arena/tank/bots/README.md +24 -0
  101. package/eval/arena/tank/bots/half1/deepseek-v4.1-flash.cpp +367 -0
  102. package/eval/arena/tank/bots/half1/doubao-seed-evolving.cpp +402 -0
  103. package/eval/arena/tank/bots/half1/gemini-4-argon.cpp +1132 -0
  104. package/eval/arena/tank/bots/half1/glm-5.3.cpp +551 -0
  105. package/eval/arena/tank/bots/half1/gpt-6-astra.cpp +233 -0
  106. package/eval/arena/tank/bots/half1/gpt-6.1-sol-ultrafast.cpp +350 -0
  107. package/eval/arena/tank/bots/half1/gpt-6.1-sol.cpp +78 -0
  108. package/eval/arena/tank/bots/half1/grok-4.7.cpp +1099 -0
  109. package/eval/arena/tank/bots/half1/haiku-5.5.cpp +210 -0
  110. package/eval/arena/tank/bots/half1/kimi-k3.cpp +353 -0
  111. package/eval/arena/tank/bots/half1/mimo-v2.6-pro.cpp +336 -0
  112. package/eval/arena/tank/bots/half1/muse-wonder-full.cpp +560 -0
  113. package/eval/arena/tank/bots/half1/opus-5.5.cpp +435 -0
  114. package/eval/arena/tank/bots/half1/qwen-3.8-max.cpp +537 -0
  115. package/eval/arena/tank/bots/half2/deepseek-v4.1-flash.cpp +425 -0
  116. package/eval/arena/tank/bots/half2/doubao-seed-evolving.cpp +308 -0
  117. package/eval/arena/tank/bots/half2/gemini-4-argon.cpp +1390 -0
  118. package/eval/arena/tank/bots/half2/glm-5.3.cpp +651 -0
  119. package/eval/arena/tank/bots/half2/gpt-6-astra.cpp +241 -0
  120. package/eval/arena/tank/bots/half2/gpt-6.1-sol-ultrafast.cpp +356 -0
  121. package/eval/arena/tank/bots/half2/gpt-6.1-sol.cpp +87 -0
  122. package/eval/arena/tank/bots/half2/grok-4.7.cpp +1205 -0
  123. package/eval/arena/tank/bots/half2/haiku-5.5.cpp +210 -0
  124. package/eval/arena/tank/bots/half2/kimi-k3.cpp +487 -0
  125. package/eval/arena/tank/bots/half2/mimo-v2.6-pro.cpp +336 -0
  126. package/eval/arena/tank/bots/half2/muse-wonder-full.cpp +560 -0
  127. package/eval/arena/tank/bots/half2/opus-5.5.cpp +476 -0
  128. package/eval/arena/tank/bots/half2/qwen-3.8-max.cpp +392 -0
  129. package/eval/arena/tank/tournament/README.md +24 -0
  130. package/eval/arena/tank/tournament/bt.py +218 -0
  131. package/eval/arena/tank/tournament/final.json +202 -0
  132. package/eval/arena/tank/tournament/halftime.json +146 -0
  133. package/eval/arena/tank/tournament/progress.json +911 -0
  134. package/eval/arena/tank/tournament/schedule.py +100 -0
  135. package/eval/arena/tank/tournament/sparring.cpp +928 -0
  136. package/eval/arena/tank/tournament/tournament.py +1426 -0
  137. package/eval/arena/tank/workspace/README.md +169 -0
  138. package/eval/arena/tank/workspace/RULES.md +443 -0
  139. package/eval/arena/tank/workspace/arena.py +862 -0
  140. package/eval/arena/tank/workspace/baseline.cpp +259 -0
  141. package/eval/arena/tank/workspace/clock.py +103 -0
  142. package/eval/arena/tank/workspace/core.py +712 -0
  143. package/eval/arena/tank/workspace/engine.py +573 -0
  144. package/eval/arena/tank/workspace/history/.lock +0 -0
  145. package/eval/arena/tank/workspace/history/20261017-193610-08715287f91f-bot.cpp +109 -0
  146. package/eval/arena/tank/workspace/history/20261017-193619-f00b2d5635fa-gpt-6-astra.cpp +241 -0
  147. package/eval/arena/tank/workspace/history/20261017-193643-63319e0a2b1a-gpt-6_1-sol-ultrafast.cpp +356 -0
  148. package/eval/arena/tank/workspace/history/20261017-193647-41c1341d0a8a-gemini-4-argon.cpp +1390 -0
  149. package/eval/arena/tank/workspace/history/20261017-193652-e34c0f45539a-gpt-6_1-sol.cpp +87 -0
  150. package/eval/arena/tank/workspace/history/20261017-193654-c026348ce96e-opus-5_5.cpp +476 -0
  151. package/eval/arena/tank/workspace/history/20261017-193655-4a21366e94d1-muse-wonder-full.cpp +560 -0
  152. package/eval/arena/tank/workspace/history/20261017-193657-dbc4e9969c52-mimo-v2_6-pro.cpp +336 -0
  153. package/eval/arena/tank/workspace/history/20261017-193703-723cd036eb31-grok-4_7.cpp +1205 -0
  154. package/eval/arena/tank/workspace/history/20261017-193707-1ebbb227be68-deepseek-v4_1-flash.cpp +425 -0
  155. package/eval/arena/tank/workspace/history/20261017-193709-aeb0e7202ad5-doubao-seed-evolving.cpp +308 -0
  156. package/eval/arena/tank/workspace/history/20261017-193710-b21ebeb9bc76-kimi-k3.cpp +487 -0
  157. package/eval/arena/tank/workspace/history/20261017-193712-54cd86531905-haiku-5_5.cpp +210 -0
  158. package/eval/arena/tank/workspace/history/20261017-193714-efdb8e35c0bf-qwen-3_8-max.cpp +392 -0
  159. package/eval/arena/tank/workspace/history/20261017-193715-681423c23fdb-glm-5_3.cpp +651 -0
  160. package/eval/arena/tank/workspace/history/20261017-193717-1a8b3c75181d-sparring.cpp +928 -0
  161. package/eval/arena/tank/workspace/history/20261017-193720-1e0803a541e8-baseline.cpp +259 -0
  162. package/eval/arena/tank/workspace/history/log.jsonl +103 -0
  163. package/eval/arena/tank/workspace/judge/cycles.hpp +53 -0
  164. package/eval/arena/tank/workspace/judge/fast.cpp +337 -0
  165. package/eval/arena/tank/workspace/judge/referee.cpp +125 -0
  166. package/eval/arena/tank/workspace/judge/speed.cpp +99 -0
  167. package/eval/arena/tank/workspace/run.cmd +31 -0
  168. package/eval/arena/tank/workspace/run.sh +23 -0
  169. package/eval/arena/tank/workspace/tank.hpp +1762 -0
  170. package/eval/arena/tank/workspace/tank_main.cpp +215 -0
  171. package/eval/arena/tank/workspace/viewer.html +1150 -0
  172. package/eval/arena/warehouse/workspace/SPEC.md +130 -0
  173. package/eval/arena/warehouse/workspace/TASK.md +71 -0
  174. package/eval/arena/warehouse/workspace/baseline/common.h +88 -0
  175. package/eval/arena/warehouse/workspace/baseline/main.cpp +27 -0
  176. package/eval/arena/warehouse/workspace/baseline/old_stuff.cpp +87 -0
  177. package/eval/arena/warehouse/workspace/baseline/sim.cpp +427 -0
  178. package/eval/arena/warehouse/workspace/baseline/util.cpp +220 -0
  179. package/eval/arena/warehouse/workspace/clock.py +42 -0
  180. package/eval/arena/warehouse/workspace/src/common.h +88 -0
  181. package/eval/arena/warehouse/workspace/src/main.cpp +27 -0
  182. package/eval/arena/warehouse/workspace/src/old_stuff.cpp +87 -0
  183. package/eval/arena/warehouse/workspace/src/sim.cpp +427 -0
  184. package/eval/arena/warehouse/workspace/src/util.cpp +220 -0
  185. package/eval/arena/warehouse/workspace/tools/bench.py +119 -0
  186. package/eval/arena/warehouse/workspace/tools/check_input.py +161 -0
  187. package/eval/arena/warehouse/workspace/tools/gen.py +132 -0
  188. package/eval/arena/warehouse/workspace/tools/measure.py +107 -0
  189. package/eval/arena/warehouse/workspace/workloads/public/pub_crowded.in +1420 -0
  190. package/eval/arena/warehouse/workspace/workloads/public/pub_crowded.out +9162 -0
  191. package/eval/arena/warehouse/workspace/workloads/public/pub_dynamic.in +1465 -0
  192. package/eval/arena/warehouse/workspace/workloads/public/pub_dynamic.out +3439 -0
  193. package/eval/arena/warehouse/workspace/workloads/public/pub_flood.in +2757 -0
  194. package/eval/arena/warehouse/workspace/workloads/public/pub_flood.out +3126 -0
  195. package/eval/arena/warehouse/workspace/workloads/public/pub_small.in +731 -0
  196. package/eval/arena/warehouse/workspace/workloads/public/pub_small.out +2578 -0
  197. package/eval/arena/warehouse/workspace/workloads/public/pub_wide.in +528 -0
  198. package/eval/arena/warehouse/workspace/workloads/public/pub_wide.out +2026 -0
  199. package/eval/bomberman.py +20 -0
  200. package/eval/commit0.py +21 -0
  201. package/eval/common.py +61 -0
  202. package/eval/ctf.py +20 -0
  203. package/eval/featurebench.py +25 -0
  204. package/eval/land.py +20 -0
  205. package/eval/legacy/bomberman-prices.json +4 -0
  206. package/eval/legacy/bomberman.py +161 -0
  207. package/eval/legacy/commit0.py +91 -0
  208. package/eval/legacy/ctf-prices.json +4 -0
  209. package/eval/legacy/ctf.py +178 -0
  210. package/eval/legacy/featurebench.py +103 -0
  211. package/eval/legacy/land-prices.json +4 -0
  212. package/eval/legacy/land.py +177 -0
  213. package/eval/legacy/swe_bench_pro.py +116 -0
  214. package/eval/legacy/tank-prices.json +3 -0
  215. package/eval/legacy/tank.py +408 -0
  216. package/eval/legacy/warehouse-prices.json +4 -0
  217. package/eval/legacy/warehouse.py +365 -0
  218. package/eval/swe-bench-pro.py +25 -0
  219. package/eval/tank.py +20 -0
  220. package/eval/warehouse.py +20 -0
  221. package/lib/client/index.js +376 -63
  222. package/lib/client/index.js.map +1 -1
  223. package/lib/index.js +684 -27
  224. package/package.json +2 -1
@@ -0,0 +1,240 @@
1
+ #define MOVE_MODE 1
2
+ // Independently implemented challenger strategy. No baseline/opponent source reuse.
3
+ // Public input only; deterministic time-expanded reachability and escape denial.
4
+ #include <algorithm>
5
+ #include <array>
6
+ #include <cmath>
7
+ #include <iostream>
8
+ #include <queue>
9
+ #include <string>
10
+ #include <vector>
11
+ using namespace std;
12
+ const string DS="UDLRS";const int DX[]={0,0,-1,1,0},DY[]={-1,1,0,0,0};
13
+ int N,P,ME,T,LIMIT,FUSE,RANGE,CAP,FIRE,SHRINK,EVERY;
14
+ struct Player{int x,y,alive;};struct Bomb{int owner,x,y,at;};struct Flame{int x,y,end;};
15
+ vector<string> board;vector<Player> players;vector<Bomb> bombs;vector<Flame> flames;
16
+ int cell(int x,int y){return y*N+x;}
17
+ struct Prediction{vector<vector<unsigned char>> safe,open,occupied;};
18
+ Prediction predict(vector<Bomb> bs,int horizon){
19
+ Prediction out;out.safe=out.open=out.occupied=vector(horizon,vector<unsigned char>(N*N));
20
+ auto grid=board;vector<int> expires(N*N,-1);for(auto f:flames)expires[cell(f.x,f.y)]=f.end;
21
+ for(int k=0;k<horizon;k++){
22
+ int now=T+k,layer=now<SHRINK?0:1+(now-SHRINK)/EVERY;
23
+ for(int y=0;y<N;y++)for(int x=0;x<N;x++)if(min({x,y,N-1-x,N-1-y})<=layer){grid[y][x]='#';expires[cell(x,y)]=-1;}
24
+ bs.erase(remove_if(bs.begin(),bs.end(),[&](Bomb b){return grid[b.y][b.x]=='#';}),bs.end());
25
+ vector<int> at(N*N,-1);for(int i=0;i<(int)bs.size();i++){at[cell(bs[i].x,bs[i].y)]=i;out.occupied[k][cell(bs[i].x,bs[i].y)]=1;}
26
+ for(int y=0;y<N;y++)for(int x=0;x<N;x++)out.open[k][cell(x,y)]=grid[y][x]=='.';
27
+ queue<int> q;vector<bool> gone(bs.size());vector<unsigned char> hit(N*N);
28
+ for(int i=0;i<(int)bs.size();i++)if(bs[i].at<=now||expires[cell(bs[i].x,bs[i].y)]>=now)q.push(i);
29
+ while(!q.empty()){
30
+ int i=q.front();q.pop();if(gone[i])continue;gone[i]=true;auto b=bs[i];hit[cell(b.x,b.y)]=1;
31
+ for(int d=0;d<4;d++)for(int r=1;r<=RANGE;r++){
32
+ int x=b.x+DX[d]*r,y=b.y+DY[d]*r;if(x<0||y<0||x>=N||y>=N||grid[y][x]=='#')break;
33
+ int c=cell(x,y);hit[c]=1;if(grid[y][x]=='+')break;if(at[c]>=0){q.push(at[c]);break;}
34
+ }
35
+ }
36
+ vector<Bomb> left;for(int i=0;i<(int)bs.size();i++)if(!gone[i])left.push_back(bs[i]);bs=move(left);
37
+ for(int y=0;y<N;y++)for(int x=0;x<N;x++){
38
+ int c=cell(x,y);if(hit[c]){expires[c]=max(expires[c],now+FIRE-1);if(grid[y][x]=='+')grid[y][x]='.';}
39
+ out.safe[k][c]=grid[y][x]=='.'&&expires[c]<now;
40
+ }
41
+ }
42
+ return out;
43
+ }
44
+ vector<double> potential;
45
+ struct Reach {array<int,5> count{},life{};array<double,5> utility{};int unique=0;};
46
+ Reach reach(const Prediction& pr,int who,int forced=-1){
47
+ vector<unsigned char> current(N*N);auto player=players[who];current[cell(player.x,player.y)]=31;
48
+ Reach result;result.utility.fill(-1e6);
49
+ for(int k=0;k<(int)pr.safe.size();k++){
50
+ vector<unsigned char> next(N*N);int now=T+k,layer=now<SHRINK?0:1+(now-SHRINK)/EVERY;
51
+ for(int c=0;c<N*N;c++)if(current[c]){
52
+ int x=c%N,y=c/N;if(min({x,y,N-1-x,N-1-y})<=layer)continue;
53
+ for(int d=0;d<5;d++){
54
+ int nx=x+DX[d],ny=y+DY[d],nc=c;
55
+ if(nx>=0&&ny>=0&&nx<N&&ny<N){int z=cell(nx,ny);if(pr.open[k][z]&&(!pr.occupied[k][z]||z==c))nc=z;}
56
+ if(k==0&&forced>=0)nc=forced;
57
+ if(pr.safe[k][nc])next[nc]|=k==0?(1<<d):current[c];
58
+ }
59
+ }
60
+ current=move(next);for(int c=0;c<N*N;c++)if(current[c])for(int d=0;d<5;d++)if(current[c]&(1<<d))result.life[d]=k+1;
61
+ }
62
+ for(int c=0;c<N*N;c++)if(current[c]){
63
+ result.unique++;
64
+ for(int d=0;d<5;d++)if(current[c]&(1<<d)){result.count[d]++;result.utility[d]=max(result.utility[d],potential.empty()?0:potential[c]);}
65
+ }
66
+ return result;
67
+ }
68
+ int blast_value(int x,int y){
69
+ int score=0;
70
+ for(int d=0;d<4;d++)for(int r=1;r<=RANGE;r++){
71
+ int nx=x+DX[d]*r,ny=y+DY[d]*r;if(nx<0||ny<0||nx>=N||ny>=N||board[ny][nx]=='#')break;
72
+ if(board[ny][nx]=='+'){score+=3;break;}
73
+ for(int i=0;i<P;i++)if(i!=ME&&players[i].alive&&players[i].x==nx&&players[i].y==ny)score+=4;
74
+ }
75
+ return score;
76
+ }
77
+ void make_potential(){
78
+ potential.assign(N*N,-1e6);priority_queue<pair<double,int>> q;
79
+ for(int y=0;y<N;y++)for(int x=0;x<N;x++)if(board[y][x]=='.'){
80
+ int edge=min({x,y,N-1-x,N-1-y});double val=blast_value(x,y)*.9;
81
+ int until=SHRINK+max(0,edge-1)*EVERY-T;
82
+ if(until<12)val-=30;else val+=min(4.0,edge*.4);
83
+ potential[cell(x,y)]=val;q.push({val,cell(x,y)});
84
+ }
85
+ // Reverse distance transform rewards approaching a useful bombing position,
86
+ // respecting walls/crates rather than using straight-line Manhattan distance.
87
+ while(!q.empty()){
88
+ auto [v,c]=q.top();q.pop();if(v<potential[c]-1e-9)continue;
89
+ int x=c%N,y=c/N;for(int d=0;d<4;d++){int nx=x+DX[d],ny=y+DY[d];if(nx<0||ny<0||nx>=N||ny>=N||board[ny][nx]!='.')continue;
90
+ int nc=cell(nx,ny);if(v-.8>potential[nc]){potential[nc]=v-.8;q.push({v-.8,nc});}
91
+ }
92
+ }
93
+ }
94
+
95
+ // Experiment: persistent target + immediate progress + decaying visit memory.
96
+ // AGGRESSIVE adds opponent pursuit and more reward for reducing enemy escapes.
97
+ vector<int> recent;int goal=-1,goal_turn=-100,last_x=-1,last_y=-1;
98
+ vector<int> goal_distance;
99
+ vector<int> distances(int start){
100
+ vector<int> dist(N*N,10000);queue<int> q;dist[start]=0;q.push(start);
101
+ while(!q.empty()){int c=q.front();q.pop();for(int d=0;d<4;d++){
102
+ int x=c%N+DX[d],y=c/N+DY[d];if(x<0||y<0||x>=N||y>=N||board[y][x]!='.')continue;
103
+ int z=cell(x,y);if(dist[z]>dist[c]+1){dist[z]=dist[c]+1;q.push(z);}
104
+ }}return dist;
105
+ }
106
+ double visits(int c){double sum=0;for(int k=0;k<(int)recent.size();k++)if(recent[k]==c)sum+=1.0/(1+.15*(recent.size()-1-k));return sum;}
107
+ void update_goal(){
108
+ int current=cell(players[ME].x,players[ME].y);recent.push_back(current);if(recent.size()>32)recent.erase(recent.begin());
109
+ auto dist=distances(current);vector<int> enemy(N*N,10000);
110
+ #ifdef AGGRESSIVE
111
+ for(int i=0;i<P;i++)if(i!=ME&&players[i].alive){auto dd=distances(cell(players[i].x,players[i].y));for(int c=0;c<N*N;c++)enemy[c]=min(enemy[c],dd[c]);}
112
+ #endif
113
+ int layer=T<SHRINK?0:1+(T-SHRINK)/EVERY;
114
+ if(goal<0||board[goal/N][goal%N]!='.'||dist[goal]>=10000||T-goal_turn>=12||
115
+ min({goal%N,goal/N,N-1-goal%N,N-1-goal/N})<=layer+1||current==goal){
116
+ goal=-1;double best=-1e20;
117
+ for(int c=0;c<N*N;c++)if(dist[c]<10000){
118
+ int x=c%N,y=c/N,edge=min({x,y,N-1-x,N-1-y});
119
+ double v=1.5*blast_value(x,y)-.65*dist[c]-.9*visits(c);
120
+ int until=SHRINK+max(0,edge-1)*EVERY-T;
121
+ if(until<=dist[c]+10)v-=40;else v+=min(3.0,edge*.3);
122
+ #ifdef AGGRESSIVE
123
+ if(enemy[c]<10000)v+=max(-12.0,9.0-1.4*abs(enemy[c]-3));
124
+ #endif
125
+ if(v>best){best=v;goal=c;}
126
+ }goal_turn=T;
127
+ }goal_distance=distances(goal<0?current:goal);
128
+ }
129
+
130
+
131
+ vector<int> previous_cells;
132
+ int predict_destination(const Prediction& pr,int my_direction){
133
+ vector<int> ids,origin,target;
134
+ for(int i=0;i<P;i++)if(players[i].alive&&pr.open[0][cell(players[i].x,players[i].y)]){
135
+ auto p=players[i];int old=cell(p.x,p.y),d=4;
136
+ if(i==ME)d=my_direction;
137
+ else if(!previous_cells.empty())for(int j=0;j<4;j++)if(old==previous_cells[i]+DY[j]*N+DX[j])d=j;
138
+ int x=p.x+DX[d],y=p.y+DY[d],dest=old;
139
+ if(x>=0&&y>=0&&x<N&&y<N&&pr.open[0][cell(x,y)]&&(!pr.occupied[0][cell(x,y)]||old==cell(x,y)))dest=cell(x,y);
140
+ ids.push_back(i);origin.push_back(old);target.push_back(dest);
141
+ }
142
+ auto result=target;
143
+ #if MOVE_MODE != 0
144
+ for(int i=0;i<(int)ids.size();i++)if(count(target.begin(),target.end(),target[i])>1)result[i]=origin[i];
145
+ #if MOVE_MODE == 2
146
+ for(int i=0;i<(int)ids.size();i++)for(int j=0;j<i;j++)if(result[i]==origin[j]&&result[j]==origin[i]){result[i]=origin[i];result[j]=origin[j];}
147
+ #endif
148
+ bool changed=true;while(changed){changed=false;auto last=result;
149
+ for(int i=0;i<(int)ids.size();i++)for(int j=0;j<(int)ids.size();j++)if(last[j]==origin[j]&&result[i]==origin[j]&&result[i]!=origin[i]){result[i]=origin[i];changed=true;}
150
+ }
151
+ #endif
152
+ for(int i=0;i<(int)ids.size();i++)if(ids[i]==ME)return result[i];
153
+ return cell(players[ME].x,players[ME].y);
154
+ }
155
+
156
+ pair<int,int> decide(){
157
+ int horizon=min(8,LIMIT-T);auto me=players[ME];make_potential();update_goal();
158
+ auto normal=predict(bombs,horizon);auto ordinary=reach(normal,ME);
159
+ vector<int> enemy_base(P);for(int i=0;i<P;i++)if(i!=ME&&players[i].alive)enemy_base[i]=reach(normal,i).unique;
160
+ int active=0;bool occupied=false,shared=false;for(auto b:bombs){active+=b.owner==ME;occupied|=b.x==me.x&&b.y==me.y;}
161
+ for(int i=0;i<P;i++)if(i!=ME&&players[i].alive&&players[i].x==me.x&&players[i].y==me.y)shared=true;
162
+ pair<int,int> answer={4,0};double best=-1e30;
163
+ for(int place=0;place<=1;place++){
164
+ if(place&&(active>=CAP||occupied||shared))continue;
165
+ auto bs=bombs;if(place)bs.push_back({ME,me.x,me.y,T+FUSE});
166
+ auto scenario=place?predict(bs,horizon):normal;auto own=place?reach(scenario,ME):ordinary;
167
+ double denial=0;
168
+ if(place){
169
+ denial=blast_value(me.x,me.y)*.7-2.5;
170
+ for(int i=0;i<P;i++)if(i!=ME&&players[i].alive&&enemy_base[i]>0){
171
+ int remaining=reach(scenario,i).unique;
172
+ denial+=remaining==0?100:5.0*max(0.0,1.0-double(remaining)/enemy_base[i]);
173
+ }
174
+ }
175
+ // Counterfactual: nearest rival may place a bomb NOW. This is a threat model,
176
+ // not an oracle or an opponent-specific policy; recomputed from public state.
177
+ int threat=-1,dist=1000;
178
+ for(int i=0;i<P;i++)if(i!=ME&&players[i].alive){
179
+ auto e=players[i];int distance=abs(e.x-me.x)+abs(e.y-me.y),count=0;bool blocked=false;
180
+ for(auto b:bs){count+=b.owner==i;blocked|=b.x==e.x&&b.y==e.y;}
181
+ if(distance<=7&&distance<dist&&count<CAP&&!blocked){threat=i;dist=distance;}
182
+ }
183
+ Reach cautious=own;
184
+ if(threat>=0){auto e=players[threat];auto other=bs;other.push_back({threat,e.x,e.y,T+FUSE});cautious=reach(predict(other,horizon),ME);}
185
+ for(int d=0;d<5;d++){
186
+ int predicted=predict_destination(scenario,d);
187
+ Reach adjusted=own;
188
+ #if MOVE_MODE != 0
189
+ adjusted=reach(scenario,ME,predicted);
190
+ #endif
191
+ double score=adjusted.life[d]*10000;
192
+ if(adjusted.count[d]>0){
193
+ score+=.25*adjusted.utility[d]+1.1*log(1.0+adjusted.count[d])+denial;
194
+ #ifdef AGGRESSIVE
195
+ if(place)score+=max(0.0,denial)*.7;
196
+ #endif
197
+ int tx=me.x+DX[d],ty=me.y+DY[d],dest=cell(me.x,me.y);
198
+ if(tx>=0&&ty>=0&&tx<N&&ty<N&&scenario.open[0][cell(tx,ty)]&&
199
+ (!scenario.occupied[0][cell(tx,ty)]||dest==cell(tx,ty)))dest=cell(tx,ty);
200
+ dest=predicted;
201
+ score-=1.3*min(50,goal_distance[dest])+1.8*visits(dest);
202
+ if(dest==cell(me.x,me.y))score-=.6;
203
+
204
+ if(cautious.count[d]==0)score-=35+5*(horizon-cautious.life[d]);
205
+ else score+=.7*log(1.0+cautious.count[d]);
206
+ int nx=me.x+DX[d],ny=me.y+DY[d];
207
+ for(int i=0;i<P;i++)if(i!=ME&&players[i].alive&&players[i].x==nx&&players[i].y==ny)score-=1.5;
208
+ }
209
+ #if MOVE_MODE != 0
210
+ int intended=cell(me.x+DX[d],me.y+DY[d]);
211
+ bool contested=false;
212
+ for(int i=0;i<P;i++)if(i!=ME&&players[i].alive&&abs(players[i].x-(me.x+DX[d]))+abs(players[i].y-(me.y+DY[d]))<=1)contested=true;
213
+ if(contested&&d!=4){auto blocked=reach(scenario,ME,cell(me.x,me.y));if(blocked.life[d]<horizon)score-=80;else score-=1;}
214
+ #endif
215
+ score-=.002*((d+T)%5);
216
+ if(score>best){best=score;answer={d,place};}
217
+ }
218
+ }
219
+ if(answer.second)goal=-1;
220
+ previous_cells.clear();for(auto p:players)previous_cells.push_back(cell(p.x,p.y));
221
+ return answer;
222
+ }
223
+ bool read(){string word;if(!(cin>>word>>T))return false;cin>>word;for(auto &row:board)cin>>row;
224
+ for(int i=0,id;i<P;i++){cin>>word>>id;cin>>players[id].x>>players[id].y>>players[id].alive;}
225
+ int k;cin>>word>>k;bombs.resize(k);for(auto &b:bombs)cin>>word>>b.owner>>b.x>>b.y>>b.at;
226
+ cin>>word>>k;flames.resize(k);for(auto &f:flames)cin>>word>>f.x>>f.y>>f.end;
227
+ // This baseline keeps its strategy; consume the public score/source extension.
228
+ while(cin>>word){if(word=="END")return true;string line;getline(cin,line);}
229
+ return false;
230
+ }
231
+ int main(int argc,char**argv){ios::sync_with_stdio(false);cin.tie(nullptr);string word;
232
+ if(!(cin>>word>>N>>P>>ME>>LIMIT>>FUSE>>RANGE>>CAP>>FIRE>>SHRINK>>EVERY))return 0;
233
+ board.resize(N);players.resize(P);
234
+ while(read()){
235
+ if(argc>1&&string(argv[1])=="--forecast-check"){
236
+ auto pr=predict(bombs,min(7,LIMIT-T));for(auto &grid:pr.safe){for(int bit:grid)cout<<bit;cout<<'\n';}cout.flush();
237
+ }else{auto [d,b]=decide();cout<<T<<' '<<DS[d]<<' '<<b<<endl;}
238
+ }
239
+ return 0;
240
+ }
@@ -0,0 +1,25 @@
1
+ """Read-only extraction clock; never uses archived modification time."""
2
+ import argparse, datetime, json, os, time
3
+ from pathlib import Path
4
+
5
+ def read_clock():
6
+ st=Path(__file__).resolve().stat()
7
+ birth=getattr(st,'st_birthtime',None)
8
+ start=birth if birth is not None else st.st_ctime
9
+ now=time.time(); elapsed=max(0,now-start)
10
+ return dict(start_utc=datetime.datetime.fromtimestamp(start,datetime.timezone.utc).isoformat(),
11
+ elapsed_seconds=round(elapsed,3),remaining_seconds=round(max(0,5400-elapsed),3),
12
+ hard_stop_reached=elapsed>=5400,
13
+ source='file_birthtime' if birth is not None or os.name=='nt' else 'file_ctime')
14
+
15
+ def main():
16
+ ap=argparse.ArgumentParser(description='Read elapsed time since extraction; writes no files.')
17
+ ap.add_argument('--json',action='store_true')
18
+ ap.add_argument('--start',action='store_true',help=argparse.SUPPRESS)
19
+ args=ap.parse_args(); data=read_clock()
20
+ if args.json: print(json.dumps(data,indent=2)); return
21
+ print('Elapsed: %.1f min | Remaining: %.1f min' % (data['elapsed_seconds']/60,data['remaining_seconds']/60))
22
+ if data['elapsed_seconds'] >= 3600 and not data['hard_stop_reached']:
23
+ print('60-minute target reached; finish validation and submit by 90 minutes.')
24
+ if data['hard_stop_reached']: print('90-minute deadline reached: submit your current bot.')
25
+ if __name__=='__main__': main()
@@ -0,0 +1,198 @@
1
+ """Match CLI: built-in bots or external persistent text-protocol processes."""
2
+ import argparse, hashlib, json, os, platform, queue, re, subprocess, sys, threading, time, signal
3
+ from collections import deque
4
+ from pathlib import Path
5
+ from engine import Game, config_for, VERSION
6
+
7
+ BASE=Path(__file__).resolve().parent
8
+ BUILTINS=set()
9
+
10
+ class ProcessBot:
11
+ def __init__(self,cmd,header):
12
+ self.q=queue.Queue(maxsize=32);self.bad=threading.Event();self.writeq=queue.Queue(maxsize=2);self.failure=''
13
+ self.stderr_tail=deque(maxlen=16)
14
+ self.p=subprocess.Popen(cmd,stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE,
15
+ creationflags=getattr(subprocess,'CREATE_NO_WINDOW',0),bufsize=0,
16
+ start_new_session=os.name!='nt')
17
+ def read_stderr():
18
+ try:
19
+ while True:
20
+ chunk=self.p.stderr.read(512)
21
+ if not chunk: break
22
+ self.stderr_tail.append(chunk)
23
+ except (OSError,ValueError): pass
24
+ threading.Thread(target=read_stderr,daemon=True).start()
25
+ def reader():
26
+ try:
27
+ while True:
28
+ line=self.p.stdout.readline(257)
29
+ if not line:
30
+ # Preserve already-received valid output, then report EOF immediately.
31
+ try:self.q.put_nowait((time.perf_counter(),None))
32
+ except queue.Full:pass
33
+ break
34
+ if len(line)>256 or not line.endswith(b'\n'):self.failure='stdout_line_too_long';self.bad.set();break
35
+ self.q.put_nowait((time.perf_counter(),line))
36
+ except (OSError,ValueError,queue.Full) as e:self.failure=type(e).__name__+' while reading stdout';self.bad.set()
37
+ threading.Thread(target=reader,daemon=True).start()
38
+ def writer():
39
+ try:
40
+ while True:
41
+ data=self.writeq.get()
42
+ if data is None:return
43
+ data=memoryview(data)
44
+ while data:
45
+ written=self.p.stdin.write(data)
46
+ if not written:raise OSError('stdin closed')
47
+ data=data[written:]
48
+ self.p.stdin.flush()
49
+ except (OSError,ValueError) as e:self.failure=type(e).__name__+' while writing stdin';self.bad.set()
50
+ threading.Thread(target=writer,daemon=True).start()
51
+ self.header=header
52
+ def send(self,text):
53
+ self.sent=time.perf_counter()
54
+ data=(self.header+text).encode('ascii');self.header=''
55
+ # A non-reading bot must not block the whole match on a full stdin pipe.
56
+ try:self.writeq.put_nowait(data)
57
+ except queue.Full:self.failure='stdin_backpressure';self.bad.set()
58
+ def receive(self,turn,limit):
59
+ deadline=self.sent+limit/1000
60
+ try:
61
+ if self.bad.is_set():return None,'io:'+self.failure,0
62
+ stamp,line=self.q.get(timeout=max(0,deadline-time.perf_counter()))
63
+ if line is None:return None,'stdout_closed',max(0,(stamp-self.sent)*1000)
64
+ if stamp<self.sent or stamp>deadline:return None,'timing',max(0,(stamp-self.sent)*1000)
65
+ match=re.fullmatch(rb'(0|[1-9][0-9]{0,5}) ([UDLRS]) ([01])\r?\n',line)
66
+ if not match or int(match[1])!=turn:return None,'protocol',(stamp-self.sent)*1000
67
+ return (match[2].decode(),int(match[3])),'',(stamp-self.sent)*1000
68
+ except queue.Empty:
69
+ if self.bad.is_set():return None,'io:'+self.failure,limit
70
+ if self.p.poll() is not None:return None,'process_exit',limit
71
+ return None,'timeout',limit
72
+ def diagnostic(self):
73
+ return dict(returncode=self.p.poll(),stderr_tail=b''.join(self.stderr_tail).decode('utf-8',errors='replace'))
74
+
75
+ def close(self):
76
+ # Terminate the bot's process group/tree, not unrelated shell or agent processes.
77
+ try:
78
+ if os.name!='nt':
79
+ try:os.killpg(self.p.pid,signal.SIGKILL)
80
+ except ProcessLookupError:pass
81
+ elif self.p.poll() is None:
82
+ try:subprocess.run(['taskkill','/PID',str(self.p.pid),'/T','/F'],stdout=subprocess.DEVNULL,
83
+ stderr=subprocess.DEVNULL,timeout=3,creationflags=getattr(subprocess,'CREATE_NO_WINDOW',0))
84
+ except (OSError,subprocess.TimeoutExpired):pass
85
+ if self.p.poll() is None:self.p.kill()
86
+ self.p.wait(timeout=3)
87
+ except (OSError,subprocess.TimeoutExpired):
88
+ pass
89
+ try:self.writeq.put_nowait(None)
90
+ except queue.Full:pass
91
+ for stream in (self.p.stdin,self.p.stdout,self.p.stderr):
92
+ try:stream.close()
93
+ except (OSError,ValueError):pass
94
+
95
+ def command(label):
96
+ p=Path(label).resolve()
97
+ if not p.exists():raise FileNotFoundError(p)
98
+ return [sys.executable,str(p)] if p.suffix=='.py' else [str(p)]
99
+
100
+ def play(labels,seed=0,replay=None,move_ms=50,initial_ms=2000):
101
+ if not 2<=len(labels)<=10:raise ValueError('between 2 and 10 bots required')
102
+ if move_ms<=0 or initial_ms<=0:raise ValueError('time limits must be positive')
103
+ pairs=[s.split('=',1) if '=' in s else [s,s] for s in labels]
104
+ names=[p[0] for p in pairs];labels=[p[1] for p in pairs]
105
+ if any(not n for n in names) or len(set(names))!=len(names):raise ValueError('bot names must be nonempty and unique')
106
+ g=Game(seed,config_for(len(labels)));frames=[g.snapshot()]
107
+ processes={};errors=[0]*len(labels);max_ms=[0.0]*len(labels);diagnostics=[]
108
+ started=time.perf_counter();initial_failures=[]
109
+ manifest={p.name:hashlib.sha256(p.read_bytes()).hexdigest() for p in [BASE/'engine.py',BASE/'arena.py']}
110
+ for i,label in enumerate(labels):
111
+ if label not in BUILTINS:
112
+ try:manifest[f'bot_{i}']=hashlib.sha256(Path(label).read_bytes()).hexdigest()
113
+ except OSError as e:manifest[f'bot_{i}']=dict(unavailable=str(e))
114
+ try:
115
+ for i,label in enumerate(labels):
116
+ if label not in BUILTINS:
117
+ try:processes[i]=ProcessBot(command(label),g.header(i))
118
+ except OSError as e:
119
+ errors[i]+=1
120
+ initial_failures.append(i);diagnostics.append(dict(turn=0,player=i,error='launch',detail=str(e)))
121
+ while not g.over():
122
+ actions=[('S',0)]*len(labels);forfeits=initial_failures[:] if g.turn==0 else []
123
+ requested=[None]*len(labels);timings=[None]*len(labels);turn_diagnostics=[]
124
+ state=g.state_text()
125
+ for i,b in processes.items():
126
+ if g.players[i]['alive']:b.send(state)
127
+ for i,b in processes.items():
128
+ if not g.players[i]['alive']:continue
129
+ a,err,ms=b.receive(g.turn,initial_ms if g.turn==0 else move_ms)
130
+ timings[i]=ms
131
+ max_ms[i]=max(max_ms[i],ms)
132
+ if err:
133
+ errors[i]+=1
134
+ detail=dict(turn=g.turn,player=i,error=err,**b.diagnostic())
135
+ diagnostics.append(detail);turn_diagnostics.append(detail)
136
+ # Fail closed on any protocol/timeout error. No stale-output resync ambiguity.
137
+ forfeits.append(i)
138
+ else:actions[i]=a;requested[i]=a
139
+ frame=g.step(actions,forfeits);frame['actions']=actions;frame['forfeits']=forfeits;frames.append(frame)
140
+ frame.update(requested_actions=requested,response_ms=timings,diagnostics=turn_diagnostics,input_text=state)
141
+ for i in list(processes):
142
+ if not g.players[i]['alive']:processes.pop(i).close()
143
+ result=g.result();result.update(names=names,bot_specs=labels,errors=errors,max_ms=max_ms,diagnostics=diagnostics,
144
+ elapsed_seconds=time.perf_counter()-started,version=VERSION)
145
+ result['hashes']=manifest
146
+ result['valid_for_scoring']=not initial_failures
147
+ result['environment']=dict(platform=platform.platform(),python=sys.version.split()[0],executable=sys.executable,
148
+ clock='time.perf_counter (monotonic wall time)',move_ms=move_ms,initial_ms=initial_ms)
149
+ if replay:
150
+ payload=dict(version=VERSION,result=result,frames=frames)
151
+ path=Path(replay);path.parent.mkdir(parents=True,exist_ok=True)
152
+ path.with_suffix('.json').write_text(json.dumps(payload,ensure_ascii=False,separators=(',',':')),encoding='utf-8')
153
+ template=(BASE.parent/'viewer/index.html').read_text(encoding='utf-8')
154
+ safe=json.dumps(payload,ensure_ascii=False,separators=(',',':')).replace('<','\\u003c')
155
+ path.with_suffix('.html').write_text(template.replace('/*REPLAY_DATA*/null',safe),encoding='utf-8')
156
+ return result
157
+ finally:
158
+ for b in processes.values():b.close()
159
+
160
+ def inspect_replay(path, turn=0, context=2):
161
+ """Plain-text replay inspection; never executes a bot or loads its code."""
162
+ data=json.loads(Path(path).read_text(encoding='utf-8'))
163
+ frames=data['frames'];names=data['result']['names']
164
+ if context<0 or context>20:raise ValueError('context must be between 0 and 20')
165
+ if not 0<=turn<len(frames)-1:raise ValueError(f'action turn must be 0..{len(frames)-2}')
166
+ lines=[f"REPLAY {data.get('version','?')} | seed={data['result']['seed']}",
167
+ 'Coordinates: x right, y down, zero-based. Each block shows the board BEFORE the action.',
168
+ 'Requested action is distinct from actual movement. Internal intentions/search are not recorded.']
169
+ for i in range(max(0,turn-context),min(len(frames)-1,turn+context+1)):
170
+ before,after=frames[i],frames[i+1]
171
+ lines += ['',f'ACTION {before["turn"]} -> STATE {after["turn"]}',
172
+ ' '+''.join(str(x%10) for x in range(len(before['board'][0])))]
173
+ lines += [f'{y:02d} {row}' for y,row in enumerate(before['board'])]
174
+ for j,(a,b) in enumerate(zip(before['players'],after['players'])):
175
+ if not a['alive']:continue
176
+ action=after.get('requested_actions',after.get('actions',[]))
177
+ request=f'{action[j][0]} place={action[j][1]}' if j<len(action) and action[j] is not None else 'no valid response'
178
+ lines.append(f'P{j} {names[j]}: ({a["x"]},{a["y"]}) requested {request}; actual ({b["x"]},{b["y"]}); alive={b["alive"]}; reason={b.get("reason","")}')
179
+ lines.append('BOMBS before: '+json.dumps(before['bombs'],ensure_ascii=False))
180
+ lines.append('FLAMES before [x,y,last_lethal_turn]: '+json.dumps(before['flames'],ensure_ascii=False))
181
+ lines.append('EVENTS after: '+json.dumps(after.get('events',[]),ensure_ascii=False))
182
+ lines.append('DIAGNOSTICS: '+json.dumps(after.get('diagnostics',[]),ensure_ascii=False))
183
+ return '\n'.join(lines)
184
+
185
+ def main():
186
+ ap=argparse.ArgumentParser();ap.add_argument('--bots',nargs='+',default=[])
187
+ ap.add_argument('--seed',type=int,default=1);ap.add_argument('--replay',default='replays/demo.html')
188
+ ap.add_argument('--move-ms',type=int,default=50);ap.add_argument('--initial-ms',type=int,default=2000)
189
+ ap.add_argument('--inspect',help='inspect a saved replay as plain text; does not run a match')
190
+ ap.add_argument('--turn',type=int,default=0);ap.add_argument('--context',type=int,default=2)
191
+ args=ap.parse_args()
192
+ if args.inspect:
193
+ try:print(inspect_replay(args.inspect,args.turn,args.context))
194
+ except (OSError,ValueError,KeyError,IndexError) as e:ap.error(str(e))
195
+ return
196
+ if not 2<=len(args.bots)<=10:ap.error('between 2 and 10 bots required')
197
+ print(json.dumps(play(args.bots,args.seed,args.replay,args.move_ms,args.initial_ms),ensure_ascii=False,indent=2))
198
+ if __name__=='__main__':main()
@@ -0,0 +1,35 @@
1
+ """Compile a bot with an existing compiler; never run the compiled program."""
2
+ import argparse,json,os,platform,shutil,subprocess,sys,tempfile,time
3
+ from pathlib import Path
4
+
5
+ def main():
6
+ ap=argparse.ArgumentParser();ap.add_argument('--compile',action='store_true');ap.add_argument('--output',default='bot.exe' if os.name=='nt' else 'bot')
7
+ ap.add_argument('--source',default='baseline.cpp',help='C++ source to compile')
8
+ args=ap.parse_args();base=Path(__file__).resolve().parent.parent
9
+ compiler=os.environ.get('CXX') or shutil.which('g++') or shutil.which('clang++')
10
+ if not compiler and os.name=='nt': # 没加进 PATH 的 w64devkit:仓库根目录下的 tools/,或 C 盘根目录
11
+ for root in [*(a/'tools' for a in base.parents),Path('C:/')]:
12
+ guess=root/'w64devkit/bin/g++.exe'
13
+ if guess.is_file():compiler=str(guess);break
14
+ info=dict(python=sys.executable,version=sys.version.split()[0],platform=platform.platform(),
15
+ standard_library_only=True,compiler=compiler,clock=time.get_clock_info('perf_counter').__dict__)
16
+ try:
17
+ with tempfile.TemporaryFile(dir=base) as f:f.write(b'probe');f.flush()
18
+ info['workspace_writable']=True
19
+ except OSError as e:info['workspace_writable']=False;info['write_error']=str(e)
20
+ if args.compile:
21
+ if not compiler:raise SystemExit('No C++ compiler found. Report the environment failure; do not install software or change permissions.')
22
+ cmd=[compiler,'-std=c++20','-O2',str(base/args.source),'-o',str(Path(args.output).resolve())]
23
+ if os.name=='nt':cmd.insert(1,'-static')
24
+ try:
25
+ subprocess.run(cmd,check=True,timeout=60)
26
+ except subprocess.TimeoutExpired:
27
+ raise SystemExit('Compilation exceeded 60 seconds; no bot was launched.')
28
+ except subprocess.CalledProcessError as e:
29
+ raise SystemExit(f'Compiler failed with exit code {e.returncode}; see diagnostics above.')
30
+ except OSError as e:
31
+ raise SystemExit(f'Cannot start compiler: {e}')
32
+ info['compiled_program']=str(Path(args.output).resolve())
33
+ print(json.dumps(info,ensure_ascii=False,indent=2))
34
+ if sys.version_info<(3,10) or not info['workspace_writable']:raise SystemExit(1)
35
+ if __name__=='__main__':main()