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.
- package/README.md +91 -13
- package/assets/bomberman/workspace.zip +0 -0
- package/assets/commit0/instance.json +30 -0
- package/assets/commit0/workspace.zip +0 -0
- package/assets/ctf/workspace.zip +0 -0
- package/assets/featurebench/hidden/test_regression.py +690 -0
- package/assets/featurebench/instance.json +16 -0
- package/assets/featurebench/workspace.zip +0 -0
- package/assets/swe-bench-pro/hidden/test.patch +36 -0
- package/assets/swe-bench-pro/instance.json +10 -0
- package/assets/swe-bench-pro/workspace.zip +0 -0
- package/assets/tank/workspace.zip +0 -0
- package/eval/README.md +18 -0
- package/eval/arena/arena/README.md +21 -0
- package/eval/arena/arena/bt.py +104 -0
- package/eval/arena/arena/clock.py +15 -0
- package/eval/arena/arena/core.py +265 -0
- package/eval/arena/arena/run.cmd +24 -0
- package/eval/arena/arena/run.sh +11 -0
- package/eval/arena/arena/schedule.py +79 -0
- package/eval/arena/arena/tournament.py +183 -0
- package/eval/arena/bomberman/bots/README.md +16 -0
- package/eval/arena/bomberman/bots/deepseek-v4.1-flash.cpp +447 -0
- package/eval/arena/bomberman/bots/doubao-seed-2.1.cpp +542 -0
- package/eval/arena/bomberman/bots/glm-5.3.cpp +406 -0
- package/eval/arena/bomberman/bots/gpt-6-astra.cpp +768 -0
- package/eval/arena/bomberman/bots/gpt-6-sol.cpp +444 -0
- package/eval/arena/bomberman/bots/kimi-k3.cpp +432 -0
- package/eval/arena/bomberman/bots/mimo-v2.6-pro.cpp +529 -0
- package/eval/arena/bomberman/bots/opus-5.5.cpp +548 -0
- package/eval/arena/bomberman/bots/qwen-3.8-max.cpp +836 -0
- package/eval/arena/bomberman/tournament/README.md +11 -0
- package/eval/arena/bomberman/tournament/bt.py +56 -0
- package/eval/arena/bomberman/tournament/evaluate.py +199 -0
- package/eval/arena/bomberman/tournament/run.py +79 -0
- package/eval/arena/bomberman/tournament/schedule-2to10.json.gz +0 -0
- package/eval/arena/bomberman/workspace/MANIFEST.json +13 -0
- package/eval/arena/bomberman/workspace/README.md +29 -0
- package/eval/arena/bomberman/workspace/RULES.md +100 -0
- package/eval/arena/bomberman/workspace/baseline.cpp +240 -0
- package/eval/arena/bomberman/workspace/clock.py +25 -0
- package/eval/arena/bomberman/workspace/engine/arena.py +198 -0
- package/eval/arena/bomberman/workspace/engine/build.py +35 -0
- package/eval/arena/bomberman/workspace/engine/engine.py +256 -0
- package/eval/arena/bomberman/workspace/engine/verify_replay.py +24 -0
- package/eval/arena/bomberman/workspace/run.cmd +28 -0
- package/eval/arena/bomberman/workspace/run.sh +16 -0
- package/eval/arena/bomberman/workspace/viewer/index.html +42 -0
- package/eval/arena/ctf/bots/README.md +18 -0
- package/eval/arena/ctf/bots/deepseek-v4.1-flash.cpp +601 -0
- package/eval/arena/ctf/bots/doubao-seed-2.1-turbo.cpp +320 -0
- package/eval/arena/ctf/bots/fable-5.1.cpp +634 -0
- package/eval/arena/ctf/bots/glm-5.3.cpp +689 -0
- package/eval/arena/ctf/bots/gpt-6-astra.cpp +657 -0
- package/eval/arena/ctf/bots/gpt-6.1-sol.cpp +725 -0
- package/eval/arena/ctf/bots/kimi-k3.cpp +667 -0
- package/eval/arena/ctf/bots/mimo-v2.6-pro.cpp +533 -0
- package/eval/arena/ctf/bots/opus-5.5.cpp +645 -0
- package/eval/arena/ctf/bots/qwen-3.8-max.cpp +726 -0
- package/eval/arena/ctf/bots/sonnet-5.5.cpp +569 -0
- package/eval/arena/ctf/tournament/README.md +15 -0
- package/eval/arena/ctf/tournament/formal-2to8.json +31 -0
- package/eval/arena/ctf/tournament/formal-9to15.json +31 -0
- package/eval/arena/ctf/workspace/README.md +41 -0
- package/eval/arena/ctf/workspace/RULES.md +119 -0
- package/eval/arena/ctf/workspace/arena.py +186 -0
- package/eval/arena/ctf/workspace/baseline.cpp +257 -0
- package/eval/arena/ctf/workspace/clock.py +15 -0
- package/eval/arena/ctf/workspace/core.py +265 -0
- package/eval/arena/ctf/workspace/engine.py +389 -0
- package/eval/arena/ctf/workspace/run.cmd +24 -0
- package/eval/arena/ctf/workspace/run.sh +11 -0
- package/eval/arena/ctf/workspace/viewer.html +71 -0
- package/eval/arena/land/bots/README.md +17 -0
- package/eval/arena/land/bots/deepseek-4.1f@workbuddy#1/bot.cpp +608 -0
- package/eval/arena/land/bots/glm-5.3@workbuddy#1/bot.cpp +521 -0
- package/eval/arena/land/bots/gpt-6-astra@codex#1/bot.cpp +373 -0
- package/eval/arena/land/bots/gpt-6-sol@codex#1/bot.cpp +341 -0
- package/eval/arena/land/bots/kimi-k3@workbuddy#1/bot.cpp +492 -0
- package/eval/arena/land/bots/mimo-2.6-pro@zcode#1/bot.cpp +652 -0
- package/eval/arena/land/bots/mystery-model@zcode#1/bot.cpp +466 -0
- package/eval/arena/land/bots/opus-5.5@claude-code#1/bot.cpp +630 -0
- package/eval/arena/land/bots/sonnet-5@claude-code#1/bot.cpp +619 -0
- package/eval/arena/land/tournament/make_replay_page.py +17 -0
- package/eval/arena/land/tournament/rate.py +290 -0
- package/eval/arena/land/tournament/round_robin.py +172 -0
- package/eval/arena/land/tournament/sparring.cpp +356 -0
- package/eval/arena/land/workspace/RULES.md +114 -0
- package/eval/arena/land/workspace/TASK.md +46 -0
- package/eval/arena/land/workspace/anchors/.build/greedy_f2e6d2dea5ca +0 -0
- package/eval/arena/land/workspace/anchors/greedy.cpp +194 -0
- package/eval/arena/land/workspace/anchors/random.cpp +80 -0
- package/eval/arena/land/workspace/clock.py +64 -0
- package/eval/arena/land/workspace/engine/build.py +33 -0
- package/eval/arena/land/workspace/engine/game.py +273 -0
- package/eval/arena/land/workspace/engine/referee.py +308 -0
- package/eval/arena/land/workspace/starter/.build/bot_be32b45b4699 +0 -0
- package/eval/arena/land/workspace/starter/bot.cpp +82 -0
- package/eval/arena/land/workspace/viewer/viewer.html +210 -0
- package/eval/arena/tank/bots/README.md +24 -0
- package/eval/arena/tank/bots/half1/deepseek-v4.1-flash.cpp +367 -0
- package/eval/arena/tank/bots/half1/doubao-seed-evolving.cpp +402 -0
- package/eval/arena/tank/bots/half1/gemini-4-argon.cpp +1132 -0
- package/eval/arena/tank/bots/half1/glm-5.3.cpp +551 -0
- package/eval/arena/tank/bots/half1/gpt-6-astra.cpp +233 -0
- package/eval/arena/tank/bots/half1/gpt-6.1-sol-ultrafast.cpp +350 -0
- package/eval/arena/tank/bots/half1/gpt-6.1-sol.cpp +78 -0
- package/eval/arena/tank/bots/half1/grok-4.7.cpp +1099 -0
- package/eval/arena/tank/bots/half1/haiku-5.5.cpp +210 -0
- package/eval/arena/tank/bots/half1/kimi-k3.cpp +353 -0
- package/eval/arena/tank/bots/half1/mimo-v2.6-pro.cpp +336 -0
- package/eval/arena/tank/bots/half1/muse-wonder-full.cpp +560 -0
- package/eval/arena/tank/bots/half1/opus-5.5.cpp +435 -0
- package/eval/arena/tank/bots/half1/qwen-3.8-max.cpp +537 -0
- package/eval/arena/tank/bots/half2/deepseek-v4.1-flash.cpp +425 -0
- package/eval/arena/tank/bots/half2/doubao-seed-evolving.cpp +308 -0
- package/eval/arena/tank/bots/half2/gemini-4-argon.cpp +1390 -0
- package/eval/arena/tank/bots/half2/glm-5.3.cpp +651 -0
- package/eval/arena/tank/bots/half2/gpt-6-astra.cpp +241 -0
- package/eval/arena/tank/bots/half2/gpt-6.1-sol-ultrafast.cpp +356 -0
- package/eval/arena/tank/bots/half2/gpt-6.1-sol.cpp +87 -0
- package/eval/arena/tank/bots/half2/grok-4.7.cpp +1205 -0
- package/eval/arena/tank/bots/half2/haiku-5.5.cpp +210 -0
- package/eval/arena/tank/bots/half2/kimi-k3.cpp +487 -0
- package/eval/arena/tank/bots/half2/mimo-v2.6-pro.cpp +336 -0
- package/eval/arena/tank/bots/half2/muse-wonder-full.cpp +560 -0
- package/eval/arena/tank/bots/half2/opus-5.5.cpp +476 -0
- package/eval/arena/tank/bots/half2/qwen-3.8-max.cpp +392 -0
- package/eval/arena/tank/tournament/README.md +24 -0
- package/eval/arena/tank/tournament/bt.py +218 -0
- package/eval/arena/tank/tournament/final.json +202 -0
- package/eval/arena/tank/tournament/halftime.json +146 -0
- package/eval/arena/tank/tournament/progress.json +911 -0
- package/eval/arena/tank/tournament/schedule.py +100 -0
- package/eval/arena/tank/tournament/sparring.cpp +928 -0
- package/eval/arena/tank/tournament/tournament.py +1426 -0
- package/eval/arena/tank/workspace/README.md +169 -0
- package/eval/arena/tank/workspace/RULES.md +443 -0
- package/eval/arena/tank/workspace/arena.py +862 -0
- package/eval/arena/tank/workspace/baseline.cpp +259 -0
- package/eval/arena/tank/workspace/clock.py +103 -0
- package/eval/arena/tank/workspace/core.py +712 -0
- package/eval/arena/tank/workspace/engine.py +573 -0
- package/eval/arena/tank/workspace/history/.lock +0 -0
- package/eval/arena/tank/workspace/history/20261017-193610-08715287f91f-bot.cpp +109 -0
- package/eval/arena/tank/workspace/history/20261017-193619-f00b2d5635fa-gpt-6-astra.cpp +241 -0
- package/eval/arena/tank/workspace/history/20261017-193643-63319e0a2b1a-gpt-6_1-sol-ultrafast.cpp +356 -0
- package/eval/arena/tank/workspace/history/20261017-193647-41c1341d0a8a-gemini-4-argon.cpp +1390 -0
- package/eval/arena/tank/workspace/history/20261017-193652-e34c0f45539a-gpt-6_1-sol.cpp +87 -0
- package/eval/arena/tank/workspace/history/20261017-193654-c026348ce96e-opus-5_5.cpp +476 -0
- package/eval/arena/tank/workspace/history/20261017-193655-4a21366e94d1-muse-wonder-full.cpp +560 -0
- package/eval/arena/tank/workspace/history/20261017-193657-dbc4e9969c52-mimo-v2_6-pro.cpp +336 -0
- package/eval/arena/tank/workspace/history/20261017-193703-723cd036eb31-grok-4_7.cpp +1205 -0
- package/eval/arena/tank/workspace/history/20261017-193707-1ebbb227be68-deepseek-v4_1-flash.cpp +425 -0
- package/eval/arena/tank/workspace/history/20261017-193709-aeb0e7202ad5-doubao-seed-evolving.cpp +308 -0
- package/eval/arena/tank/workspace/history/20261017-193710-b21ebeb9bc76-kimi-k3.cpp +487 -0
- package/eval/arena/tank/workspace/history/20261017-193712-54cd86531905-haiku-5_5.cpp +210 -0
- package/eval/arena/tank/workspace/history/20261017-193714-efdb8e35c0bf-qwen-3_8-max.cpp +392 -0
- package/eval/arena/tank/workspace/history/20261017-193715-681423c23fdb-glm-5_3.cpp +651 -0
- package/eval/arena/tank/workspace/history/20261017-193717-1a8b3c75181d-sparring.cpp +928 -0
- package/eval/arena/tank/workspace/history/20261017-193720-1e0803a541e8-baseline.cpp +259 -0
- package/eval/arena/tank/workspace/history/log.jsonl +103 -0
- package/eval/arena/tank/workspace/judge/cycles.hpp +53 -0
- package/eval/arena/tank/workspace/judge/fast.cpp +337 -0
- package/eval/arena/tank/workspace/judge/referee.cpp +125 -0
- package/eval/arena/tank/workspace/judge/speed.cpp +99 -0
- package/eval/arena/tank/workspace/run.cmd +31 -0
- package/eval/arena/tank/workspace/run.sh +23 -0
- package/eval/arena/tank/workspace/tank.hpp +1762 -0
- package/eval/arena/tank/workspace/tank_main.cpp +215 -0
- package/eval/arena/tank/workspace/viewer.html +1150 -0
- package/eval/arena/warehouse/workspace/SPEC.md +130 -0
- package/eval/arena/warehouse/workspace/TASK.md +71 -0
- package/eval/arena/warehouse/workspace/baseline/common.h +88 -0
- package/eval/arena/warehouse/workspace/baseline/main.cpp +27 -0
- package/eval/arena/warehouse/workspace/baseline/old_stuff.cpp +87 -0
- package/eval/arena/warehouse/workspace/baseline/sim.cpp +427 -0
- package/eval/arena/warehouse/workspace/baseline/util.cpp +220 -0
- package/eval/arena/warehouse/workspace/clock.py +42 -0
- package/eval/arena/warehouse/workspace/src/common.h +88 -0
- package/eval/arena/warehouse/workspace/src/main.cpp +27 -0
- package/eval/arena/warehouse/workspace/src/old_stuff.cpp +87 -0
- package/eval/arena/warehouse/workspace/src/sim.cpp +427 -0
- package/eval/arena/warehouse/workspace/src/util.cpp +220 -0
- package/eval/arena/warehouse/workspace/tools/bench.py +119 -0
- package/eval/arena/warehouse/workspace/tools/check_input.py +161 -0
- package/eval/arena/warehouse/workspace/tools/gen.py +132 -0
- package/eval/arena/warehouse/workspace/tools/measure.py +107 -0
- package/eval/arena/warehouse/workspace/workloads/public/pub_crowded.in +1420 -0
- package/eval/arena/warehouse/workspace/workloads/public/pub_crowded.out +9162 -0
- package/eval/arena/warehouse/workspace/workloads/public/pub_dynamic.in +1465 -0
- package/eval/arena/warehouse/workspace/workloads/public/pub_dynamic.out +3439 -0
- package/eval/arena/warehouse/workspace/workloads/public/pub_flood.in +2757 -0
- package/eval/arena/warehouse/workspace/workloads/public/pub_flood.out +3126 -0
- package/eval/arena/warehouse/workspace/workloads/public/pub_small.in +731 -0
- package/eval/arena/warehouse/workspace/workloads/public/pub_small.out +2578 -0
- package/eval/arena/warehouse/workspace/workloads/public/pub_wide.in +528 -0
- package/eval/arena/warehouse/workspace/workloads/public/pub_wide.out +2026 -0
- package/eval/bomberman.py +20 -0
- package/eval/commit0.py +21 -0
- package/eval/common.py +61 -0
- package/eval/ctf.py +20 -0
- package/eval/featurebench.py +25 -0
- package/eval/land.py +20 -0
- package/eval/legacy/bomberman-prices.json +4 -0
- package/eval/legacy/bomberman.py +161 -0
- package/eval/legacy/commit0.py +91 -0
- package/eval/legacy/ctf-prices.json +4 -0
- package/eval/legacy/ctf.py +178 -0
- package/eval/legacy/featurebench.py +103 -0
- package/eval/legacy/land-prices.json +4 -0
- package/eval/legacy/land.py +177 -0
- package/eval/legacy/swe_bench_pro.py +116 -0
- package/eval/legacy/tank-prices.json +3 -0
- package/eval/legacy/tank.py +408 -0
- package/eval/legacy/warehouse-prices.json +4 -0
- package/eval/legacy/warehouse.py +365 -0
- package/eval/swe-bench-pro.py +25 -0
- package/eval/tank.py +20 -0
- package/eval/warehouse.py +20 -0
- package/lib/client/index.js +376 -63
- package/lib/client/index.js.map +1 -1
- package/lib/index.js +684 -27
- 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()
|