dsh-eval-guard 0.2.3 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +91 -13
- package/assets/commit0/instance.json +30 -0
- package/assets/commit0/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/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,194 @@
|
|
|
1
|
+
// Anchor bot "greedy": fixed-pattern rectangle loops, ignores the opponents.
|
|
2
|
+
#include <bits/stdc++.h>
|
|
3
|
+
using namespace std;
|
|
4
|
+
|
|
5
|
+
const int DX[4] = {0, 0, -1, 1};
|
|
6
|
+
const int DY[4] = {-1, 1, 0, 0};
|
|
7
|
+
const char DC[4] = {'U', 'D', 'L', 'R'};
|
|
8
|
+
|
|
9
|
+
int dirIndex(char c) { return string("UDLR").find(c); }
|
|
10
|
+
|
|
11
|
+
struct Player {
|
|
12
|
+
int x, y, dir, trailLen, area, deaths;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
struct State {
|
|
16
|
+
int turn;
|
|
17
|
+
vector<Player> p;
|
|
18
|
+
vector<string> owner; // owner[y][x]: '.' neutral, '0'..'3' territory of that player
|
|
19
|
+
vector<string> trail; // trail[y][x]: '.' none, '0'..'3' trail of that player
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
int W, H, MAX_TURNS, N, ME, MOVE_MS, INIT_MS;
|
|
23
|
+
vector<pair<int, int>> spawn;
|
|
24
|
+
|
|
25
|
+
bool readState(State& s) {
|
|
26
|
+
string tok;
|
|
27
|
+
if (!(cin >> tok)) return false; // "TURN"
|
|
28
|
+
cin >> s.turn;
|
|
29
|
+
s.p.assign(N, {});
|
|
30
|
+
for (int i = 0; i < N; i++) {
|
|
31
|
+
int id;
|
|
32
|
+
char d;
|
|
33
|
+
Player q;
|
|
34
|
+
cin >> tok >> id >> q.x >> q.y >> d >> q.trailLen >> q.area >> q.deaths;
|
|
35
|
+
q.dir = dirIndex(d);
|
|
36
|
+
s.p[id] = q;
|
|
37
|
+
}
|
|
38
|
+
cin >> tok; // OWNER
|
|
39
|
+
s.owner.assign(H, "");
|
|
40
|
+
for (auto& row : s.owner) cin >> row;
|
|
41
|
+
cin >> tok; // TRAIL
|
|
42
|
+
s.trail.assign(H, "");
|
|
43
|
+
for (auto& row : s.trail) cin >> row;
|
|
44
|
+
cin >> tok; // END
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Greedy loop maker: leave the territory, draw the largest rectangle it can find,
|
|
49
|
+
// return home by the shortest safe path. Ignores the opponents, except that it
|
|
50
|
+
// always takes an immediate chance to cut an opponent's trail.
|
|
51
|
+
mt19937 rng(20260925);
|
|
52
|
+
deque<int> plan;
|
|
53
|
+
bool wasOutside = false;
|
|
54
|
+
|
|
55
|
+
bool inb(int x, int y) { return x >= 0 && x < W && y >= 0 && y < H; }
|
|
56
|
+
|
|
57
|
+
// BFS from the head (no reversing) through cells that are not our own trail.
|
|
58
|
+
// Returns the direction path to the nearest cell satisfying goal(x, y); empty if none.
|
|
59
|
+
template <class F>
|
|
60
|
+
vector<int> bfsPath(const State& s, F goal) {
|
|
61
|
+
const Player& me = s.p[ME];
|
|
62
|
+
char myTrail = '0' + ME;
|
|
63
|
+
vector<int> from(W * H, -1), fdir(W * H, -1);
|
|
64
|
+
deque<int> q;
|
|
65
|
+
int start = me.y * W + me.x;
|
|
66
|
+
from[start] = start;
|
|
67
|
+
for (int d = 0; d < 4; d++) {
|
|
68
|
+
if (d == (me.dir ^ 1)) continue;
|
|
69
|
+
int nx = me.x + DX[d], ny = me.y + DY[d];
|
|
70
|
+
if (!inb(nx, ny) || s.trail[ny][nx] == myTrail) continue;
|
|
71
|
+
int k = ny * W + nx;
|
|
72
|
+
if (from[k] != -1) continue;
|
|
73
|
+
from[k] = start;
|
|
74
|
+
fdir[k] = d;
|
|
75
|
+
q.push_back(k);
|
|
76
|
+
}
|
|
77
|
+
while (!q.empty()) {
|
|
78
|
+
int k = q.front();
|
|
79
|
+
q.pop_front();
|
|
80
|
+
int x = k % W, y = k / W;
|
|
81
|
+
if (goal(x, y)) {
|
|
82
|
+
vector<int> path;
|
|
83
|
+
for (int c = k; c != start; c = from[c]) path.push_back(fdir[c]);
|
|
84
|
+
reverse(path.begin(), path.end());
|
|
85
|
+
return path;
|
|
86
|
+
}
|
|
87
|
+
for (int d = 0; d < 4; d++) {
|
|
88
|
+
int nx = x + DX[d], ny = y + DY[d];
|
|
89
|
+
if (!inb(nx, ny) || s.trail[ny][nx] == myTrail) continue;
|
|
90
|
+
int nk = ny * W + nx;
|
|
91
|
+
if (from[nk] != -1) continue;
|
|
92
|
+
from[nk] = k;
|
|
93
|
+
fdir[nk] = d;
|
|
94
|
+
q.push_back(nk);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return {};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
void makePlan(const State& s) {
|
|
101
|
+
char mine = '0' + ME;
|
|
102
|
+
vector<int> path = bfsPath(s, [&](int x, int y) { return s.owner[y][x] != mine; });
|
|
103
|
+
if (path.empty()) return;
|
|
104
|
+
int ex = s.p[ME].x, ey = s.p[ME].y;
|
|
105
|
+
for (int d : path) ex += DX[d], ey += DY[d];
|
|
106
|
+
int d = path.back();
|
|
107
|
+
int best = -1, ba = 0, bb = 0, bs = 0;
|
|
108
|
+
for (int it = 0; it < 24; it++) {
|
|
109
|
+
int a = 3 + rng() % 6, b = 3 + rng() % 6;
|
|
110
|
+
int side = (d < 2) ? 2 + rng() % 2 : rng() % 2; // perpendicular to d
|
|
111
|
+
int fx = ex + DX[d] * (a - 1), fy = ey + DY[d] * (a - 1);
|
|
112
|
+
int cx = fx + DX[side] * b, cy = fy + DY[side] * b;
|
|
113
|
+
if (!inb(fx, fy) || !inb(cx, cy)) continue;
|
|
114
|
+
int gain = 0;
|
|
115
|
+
for (int y = min(ey, cy); y <= max(ey, cy); y++)
|
|
116
|
+
for (int x = min(ex, cx); x <= max(ex, cx); x++)
|
|
117
|
+
if (s.owner[y][x] != mine) gain++;
|
|
118
|
+
if (gain > best) best = gain, ba = a, bb = b, bs = side;
|
|
119
|
+
}
|
|
120
|
+
plan.assign(path.begin(), path.end());
|
|
121
|
+
if (best < 0) return;
|
|
122
|
+
for (int i = 0; i < ba - 1; i++) plan.push_back(d);
|
|
123
|
+
for (int i = 0; i < bb; i++) plan.push_back(bs);
|
|
124
|
+
for (int i = 0; i < ba - 1; i++) plan.push_back(d ^ 1);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
int anySafe(const State& s) {
|
|
128
|
+
const Player& me = s.p[ME];
|
|
129
|
+
char myTrail = '0' + ME;
|
|
130
|
+
for (int d : {me.dir, 0, 1, 2, 3}) {
|
|
131
|
+
if (d == (me.dir ^ 1)) continue;
|
|
132
|
+
int nx = me.x + DX[d], ny = me.y + DY[d];
|
|
133
|
+
if (inb(nx, ny) && s.trail[ny][nx] != myTrail) return d;
|
|
134
|
+
}
|
|
135
|
+
return me.dir;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
int decide(const State& s) {
|
|
139
|
+
const Player& me = s.p[ME];
|
|
140
|
+
char mine = '0' + ME;
|
|
141
|
+
bool inside = s.owner[me.y][me.x] == mine;
|
|
142
|
+
if (inside && wasOutside) plan.clear(); // just captured or respawned
|
|
143
|
+
wasOutside = !inside;
|
|
144
|
+
|
|
145
|
+
for (int d = 0; d < 4; d++) {
|
|
146
|
+
if (d == (me.dir ^ 1)) continue;
|
|
147
|
+
int nx = me.x + DX[d], ny = me.y + DY[d];
|
|
148
|
+
if (inb(nx, ny) && s.trail[ny][nx] != '.' && s.trail[ny][nx] != mine) { // any opponent's trail
|
|
149
|
+
plan.clear();
|
|
150
|
+
return d;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (plan.empty()) {
|
|
154
|
+
if (inside) makePlan(s);
|
|
155
|
+
else {
|
|
156
|
+
vector<int> home = bfsPath(s, [&](int x, int y) { return s.owner[y][x] == mine; });
|
|
157
|
+
if (!home.empty()) return home[0];
|
|
158
|
+
return anySafe(s);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (!plan.empty()) {
|
|
162
|
+
int d = plan.front();
|
|
163
|
+
int nx = me.x + DX[d], ny = me.y + DY[d];
|
|
164
|
+
if (d != (me.dir ^ 1) && inb(nx, ny) && s.trail[ny][nx] != mine) {
|
|
165
|
+
plan.pop_front();
|
|
166
|
+
return d;
|
|
167
|
+
}
|
|
168
|
+
plan.clear();
|
|
169
|
+
if (!inside) {
|
|
170
|
+
vector<int> home = bfsPath(s, [&](int x, int y) { return s.owner[y][x] == mine; });
|
|
171
|
+
if (!home.empty()) return home[0];
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return anySafe(s);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
int main() {
|
|
178
|
+
ios::sync_with_stdio(false);
|
|
179
|
+
cin.tie(nullptr);
|
|
180
|
+
string tok;
|
|
181
|
+
cin >> tok; // INIT
|
|
182
|
+
cin >> W >> H >> MAX_TURNS >> N >> ME >> MOVE_MS >> INIT_MS;
|
|
183
|
+
spawn.resize(N);
|
|
184
|
+
for (int i = 0; i < N; i++) {
|
|
185
|
+
int id, x, y;
|
|
186
|
+
cin >> tok >> id >> x >> y;
|
|
187
|
+
spawn[id] = {x, y};
|
|
188
|
+
}
|
|
189
|
+
State s;
|
|
190
|
+
while (readState(s)) {
|
|
191
|
+
int d = decide(s);
|
|
192
|
+
cout << s.turn << ' ' << DC[d] << endl; // endl flushes: required every turn
|
|
193
|
+
}
|
|
194
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// Anchor bot "random": a uniformly random move among those that do not hit a wall or its own trail.
|
|
2
|
+
#include <bits/stdc++.h>
|
|
3
|
+
using namespace std;
|
|
4
|
+
|
|
5
|
+
const int DX[4] = {0, 0, -1, 1};
|
|
6
|
+
const int DY[4] = {-1, 1, 0, 0};
|
|
7
|
+
const char DC[4] = {'U', 'D', 'L', 'R'};
|
|
8
|
+
|
|
9
|
+
int dirIndex(char c) { return string("UDLR").find(c); }
|
|
10
|
+
|
|
11
|
+
struct Player {
|
|
12
|
+
int x, y, dir, trailLen, area, deaths;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
struct State {
|
|
16
|
+
int turn;
|
|
17
|
+
vector<Player> p;
|
|
18
|
+
vector<string> owner; // owner[y][x]: '.' neutral, '0'..'3' territory of that player
|
|
19
|
+
vector<string> trail; // trail[y][x]: '.' none, '0'..'3' trail of that player
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
int W, H, MAX_TURNS, N, ME, MOVE_MS, INIT_MS;
|
|
23
|
+
vector<pair<int, int>> spawn;
|
|
24
|
+
|
|
25
|
+
bool readState(State& s) {
|
|
26
|
+
string tok;
|
|
27
|
+
if (!(cin >> tok)) return false; // "TURN"
|
|
28
|
+
cin >> s.turn;
|
|
29
|
+
s.p.assign(N, {});
|
|
30
|
+
for (int i = 0; i < N; i++) {
|
|
31
|
+
int id;
|
|
32
|
+
char d;
|
|
33
|
+
Player q;
|
|
34
|
+
cin >> tok >> id >> q.x >> q.y >> d >> q.trailLen >> q.area >> q.deaths;
|
|
35
|
+
q.dir = dirIndex(d);
|
|
36
|
+
s.p[id] = q;
|
|
37
|
+
}
|
|
38
|
+
cin >> tok; // OWNER
|
|
39
|
+
s.owner.assign(H, "");
|
|
40
|
+
for (auto& row : s.owner) cin >> row;
|
|
41
|
+
cin >> tok; // TRAIL
|
|
42
|
+
s.trail.assign(H, "");
|
|
43
|
+
for (auto& row : s.trail) cin >> row;
|
|
44
|
+
cin >> tok; // END
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
mt19937 rng(12345);
|
|
49
|
+
|
|
50
|
+
int decide(const State& s) {
|
|
51
|
+
const Player& me = s.p[ME];
|
|
52
|
+
char myTrail = '0' + ME;
|
|
53
|
+
vector<int> ok;
|
|
54
|
+
for (int d = 0; d < 4; d++) {
|
|
55
|
+
if (d == (me.dir ^ 1)) continue;
|
|
56
|
+
int nx = me.x + DX[d], ny = me.y + DY[d];
|
|
57
|
+
if (nx >= 0 && nx < W && ny >= 0 && ny < H && s.trail[ny][nx] != myTrail) ok.push_back(d);
|
|
58
|
+
}
|
|
59
|
+
if (ok.empty()) return me.dir;
|
|
60
|
+
return ok[rng() % ok.size()];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
int main() {
|
|
64
|
+
ios::sync_with_stdio(false);
|
|
65
|
+
cin.tie(nullptr);
|
|
66
|
+
string tok;
|
|
67
|
+
cin >> tok; // INIT
|
|
68
|
+
cin >> W >> H >> MAX_TURNS >> N >> ME >> MOVE_MS >> INIT_MS;
|
|
69
|
+
spawn.resize(N);
|
|
70
|
+
for (int i = 0; i < N; i++) {
|
|
71
|
+
int id, x, y;
|
|
72
|
+
cin >> tok >> id >> x >> y;
|
|
73
|
+
spawn[id] = {x, y};
|
|
74
|
+
}
|
|
75
|
+
State s;
|
|
76
|
+
while (readState(s)) {
|
|
77
|
+
int d = decide(s);
|
|
78
|
+
cout << s.turn << ' ' << DC[d] << endl; // endl flushes: required every turn
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Development timer; never infer the unpack time from file mtime.
|
|
2
|
+
Explicit organizer start is preferred. Legacy birthtime/ctime is frozen once.
|
|
3
|
+
"""
|
|
4
|
+
import argparse
|
|
5
|
+
import datetime
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
import platform
|
|
9
|
+
import time
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
MARKER = '.development-clock.json'
|
|
13
|
+
|
|
14
|
+
def boot_id():
|
|
15
|
+
try:return Path('/proc/sys/kernel/random/boot_id').read_text().strip()
|
|
16
|
+
except OSError:return None
|
|
17
|
+
|
|
18
|
+
def initialize(root, explicit=False, wall=None, monotonic=None):
|
|
19
|
+
root=Path(root);marker=root/MARKER
|
|
20
|
+
if marker.exists():return json.loads(marker.read_text(encoding='utf-8'))
|
|
21
|
+
now=time.time();mono=time.monotonic()
|
|
22
|
+
if explicit:
|
|
23
|
+
start=now if wall is None else wall
|
|
24
|
+
start_mono=mono if monotonic is None else monotonic
|
|
25
|
+
source='explicit_start'
|
|
26
|
+
else:
|
|
27
|
+
st=root.stat();start=getattr(st,'st_birthtime',None) or st.st_ctime
|
|
28
|
+
source='directory_birthtime' if getattr(st,'st_birthtime',None) or os.name=='nt' else 'directory_ctime_estimate'
|
|
29
|
+
start_mono=mono-max(0,now-start)
|
|
30
|
+
state=dict(version=1,start_unix=start,start_monotonic=start_mono,source=source,
|
|
31
|
+
host=platform.node(),system=platform.system(),boot_id=boot_id(),boot_epoch=now-mono,
|
|
32
|
+
suggested_seconds=3600,hard_stop_seconds=5400)
|
|
33
|
+
try:
|
|
34
|
+
with marker.open('x',encoding='utf-8') as f:json.dump(state,f,indent=2)
|
|
35
|
+
except FileExistsError:return json.loads(marker.read_text(encoding='utf-8'))
|
|
36
|
+
return state
|
|
37
|
+
|
|
38
|
+
def read_clock(root, explicit=False):
|
|
39
|
+
state=initialize(root,explicit);now=time.time();mono=time.monotonic();current_boot=boot_id()
|
|
40
|
+
same_host=state['host']==platform.node() and state['system']==platform.system()
|
|
41
|
+
same_boot=(state['boot_id']==current_boot if state['boot_id'] and current_boot
|
|
42
|
+
else abs((now-mono)-state['boot_epoch'])<120)
|
|
43
|
+
use_mono=same_host and same_boot and mono>=state['start_monotonic']
|
|
44
|
+
elapsed=max(0,mono-state['start_monotonic'] if use_mono else now-state['start_unix'])
|
|
45
|
+
return dict(now_utc=datetime.datetime.fromtimestamp(now,datetime.timezone.utc).isoformat(),
|
|
46
|
+
start_utc=datetime.datetime.fromtimestamp(state['start_unix'],datetime.timezone.utc).isoformat(),
|
|
47
|
+
elapsed_seconds=round(elapsed,3),remaining_seconds=round(max(0,5400-elapsed),3),
|
|
48
|
+
hard_stop_reached=elapsed>=5400,source=state['source'],
|
|
49
|
+
measurement='monotonic' if use_mono else 'persisted_UTC_fallback',
|
|
50
|
+
approximate_start=state['source']=='directory_ctime_estimate')
|
|
51
|
+
|
|
52
|
+
def main():
|
|
53
|
+
ap=argparse.ArgumentParser();ap.add_argument('--start',action='store_true',help='start once; never resets an existing timer')
|
|
54
|
+
ap.add_argument('--json',action='store_true');args=ap.parse_args()
|
|
55
|
+
data=read_clock(Path(__file__).resolve().parent,args.start)
|
|
56
|
+
if args.json:print(json.dumps(data,ensure_ascii=True,indent=2));return
|
|
57
|
+
print('Current UTC: '+data['now_utc'])
|
|
58
|
+
print('Elapsed: %.1f min | Remaining until 90-minute deadline: %.1f min | Suggested: 60 min.' %
|
|
59
|
+
(data['elapsed_seconds']/60,data['remaining_seconds']/60))
|
|
60
|
+
print('Start: %s | Clock: %s | Source: %s' % (data['start_utc'],data['measurement'],data['source']))
|
|
61
|
+
if data['approximate_start']:print('NOTE: legacy Linux ctime start is approximate; prefer the organizer unpack launcher.')
|
|
62
|
+
if data['hard_stop_reached']:print('HARD STOP REACHED: submit the current version now.')
|
|
63
|
+
|
|
64
|
+
if __name__=='__main__':main()
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Compile a bot with the official flags. The compiler is taken from $CXX (default g++)."""
|
|
2
|
+
import hashlib
|
|
3
|
+
import os
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
FLAGS = ['-O2', '-std=c++20']
|
|
8
|
+
if sys.platform == 'win32':
|
|
9
|
+
FLAGS.append('-static')
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def compile_bot(src, cache_dir=None):
|
|
13
|
+
"""Compile src (.cpp) and return the executable path. Rebuilds only when the source changes."""
|
|
14
|
+
src = os.path.abspath(src)
|
|
15
|
+
cache_dir = cache_dir or os.path.join(os.path.dirname(src), '.build')
|
|
16
|
+
os.makedirs(cache_dir, exist_ok=True)
|
|
17
|
+
with open(src, 'rb') as f:
|
|
18
|
+
digest = hashlib.sha1(f.read()).hexdigest()[:12]
|
|
19
|
+
name = os.path.splitext(os.path.basename(src))[0] + '_' + digest
|
|
20
|
+
exe = os.path.join(cache_dir, name + ('.exe' if sys.platform == 'win32' else ''))
|
|
21
|
+
if not os.path.exists(exe):
|
|
22
|
+
cmd = [os.environ.get('CXX', 'g++')] + FLAGS + ['-o', exe, src]
|
|
23
|
+
r = subprocess.run(cmd, capture_output=True, text=True)
|
|
24
|
+
if r.returncode != 0:
|
|
25
|
+
raise RuntimeError('compile failed: %s\n%s' % (' '.join(cmd), r.stderr[-4000:]))
|
|
26
|
+
return exe
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def resolve(path, cache_dir=None):
|
|
30
|
+
"""Accept a .cpp (compiled on demand) or an executable."""
|
|
31
|
+
if path.endswith('.cpp'):
|
|
32
|
+
return compile_bot(path, cache_dir)
|
|
33
|
+
return os.path.abspath(path)
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"""Territory game (圈地) rules engine.
|
|
2
|
+
|
|
3
|
+
Pure Python, no dependencies, fully deterministic. This file is the single
|
|
4
|
+
source of truth for the rules; RULES.md describes the same behaviour in prose.
|
|
5
|
+
"""
|
|
6
|
+
import random
|
|
7
|
+
|
|
8
|
+
DIRS = {'U': (0, -1), 'D': (0, 1), 'L': (-1, 0), 'R': (1, 0)}
|
|
9
|
+
OPPOSITE = {'U': 'D', 'D': 'U', 'L': 'R', 'R': 'L'}
|
|
10
|
+
|
|
11
|
+
DEFAULT_CONFIG = {
|
|
12
|
+
'width': 40,
|
|
13
|
+
'height': 40,
|
|
14
|
+
'max_turns': 600,
|
|
15
|
+
'start_radius': 2, # initial territory: (2r+1)x(2r+1) square around spawn
|
|
16
|
+
'protect_radius': 1, # protected zone: (2r+1)x(2r+1) square, can never be captured
|
|
17
|
+
'spawn_margin': 4, # min distance from spawn to the border
|
|
18
|
+
'min_spawn_dist': 16, # min Manhattan distance between any two spawns
|
|
19
|
+
'players': 2,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
# 4 人混战:正方形棋盘,四个出生点互为 90° 旋转
|
|
23
|
+
MELEE_CONFIG = dict(DEFAULT_CONFIG, width=48, height=48, players=4, spawn_margin=5)
|
|
24
|
+
|
|
25
|
+
ROT90 = {'U': 'R', 'R': 'D', 'D': 'L', 'L': 'U'} # clockwise
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def config_for(players):
|
|
29
|
+
return MELEE_CONFIG if players == 4 else DEFAULT_CONFIG
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def make_start(seed, cfg=DEFAULT_CONFIG):
|
|
33
|
+
"""Spawns and initial directions for a seed.
|
|
34
|
+
2 players: player 1 is player 0 rotated 180 degrees.
|
|
35
|
+
4 players: player k is player 0 rotated k x 90 degrees clockwise (square board)."""
|
|
36
|
+
rng = random.Random(seed)
|
|
37
|
+
W, H, m = cfg['width'], cfg['height'], cfg['spawn_margin']
|
|
38
|
+
if cfg.get('players', 2) == 4:
|
|
39
|
+
assert W == H
|
|
40
|
+
while True:
|
|
41
|
+
x, y = rng.randint(m, W - 1 - m), rng.randint(m, H - 1 - m)
|
|
42
|
+
pts = [(x, y)]
|
|
43
|
+
for _ in range(3):
|
|
44
|
+
px, py = pts[-1]
|
|
45
|
+
pts.append((W - 1 - py, px))
|
|
46
|
+
if all(abs(a[0] - b[0]) + abs(a[1] - b[1]) >= cfg['min_spawn_dist']
|
|
47
|
+
for i, a in enumerate(pts) for b in pts[i + 1:]):
|
|
48
|
+
break
|
|
49
|
+
d = rng.choice('UDLR')
|
|
50
|
+
dirs = [d]
|
|
51
|
+
for _ in range(3):
|
|
52
|
+
dirs.append(ROT90[dirs[-1]])
|
|
53
|
+
return pts, dirs
|
|
54
|
+
while True:
|
|
55
|
+
x, y = rng.randint(m, W - 1 - m), rng.randint(m, H - 1 - m)
|
|
56
|
+
x2, y2 = W - 1 - x, H - 1 - y
|
|
57
|
+
if abs(x - x2) + abs(y - y2) >= cfg['min_spawn_dist']:
|
|
58
|
+
break
|
|
59
|
+
d = rng.choice('UDLR')
|
|
60
|
+
return [(x, y), (x2, y2)], [d, OPPOSITE[d]]
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class Game:
|
|
64
|
+
def __init__(self, cfg, spawns, dirs):
|
|
65
|
+
self.cfg = cfg
|
|
66
|
+
self.W, self.H = cfg['width'], cfg['height']
|
|
67
|
+
self.N = len(spawns)
|
|
68
|
+
n = self.W * self.H
|
|
69
|
+
self.owner = [-1] * n # territory owner per cell
|
|
70
|
+
self.trail = [-1] * n # trail owner per cell
|
|
71
|
+
self.protected = [-1] * n # protected-zone owner per cell
|
|
72
|
+
self.spawns = list(spawns)
|
|
73
|
+
self.init_dirs = list(dirs)
|
|
74
|
+
self.pos = list(spawns)
|
|
75
|
+
self.dir = list(dirs)
|
|
76
|
+
self.trails = [[] for _ in range(self.N)] # ordered trail cell indices
|
|
77
|
+
self.deaths = [0] * self.N
|
|
78
|
+
self.kills = [0] * self.N
|
|
79
|
+
self.out = [False] * self.N # 被淘汰(程序崩溃或错误过多)的玩家:停在出生点,不再行动
|
|
80
|
+
self.turn = 0
|
|
81
|
+
for i, (sx, sy) in enumerate(spawns):
|
|
82
|
+
r = cfg['start_radius']
|
|
83
|
+
for y in range(sy - r, sy + r + 1):
|
|
84
|
+
for x in range(sx - r, sx + r + 1):
|
|
85
|
+
self.owner[y * self.W + x] = i
|
|
86
|
+
r = cfg['protect_radius']
|
|
87
|
+
for y in range(sy - r, sy + r + 1):
|
|
88
|
+
for x in range(sx - r, sx + r + 1):
|
|
89
|
+
self.protected[y * self.W + x] = i
|
|
90
|
+
|
|
91
|
+
def over(self):
|
|
92
|
+
return self.turn >= self.cfg['max_turns']
|
|
93
|
+
|
|
94
|
+
def areas(self):
|
|
95
|
+
a = [0] * self.N
|
|
96
|
+
for o in self.owner:
|
|
97
|
+
if o >= 0:
|
|
98
|
+
a[o] += 1
|
|
99
|
+
return a
|
|
100
|
+
|
|
101
|
+
def eliminate(self, i):
|
|
102
|
+
"""Remove a forfeited player from play (melee only): its trail is cleared, it stays
|
|
103
|
+
on its spawn and never moves again. Its territory stays on the board."""
|
|
104
|
+
for k in self.trails[i]:
|
|
105
|
+
self.trail[k] = -1
|
|
106
|
+
self.trails[i] = []
|
|
107
|
+
self.pos[i] = self.spawns[i]
|
|
108
|
+
self.out[i] = True
|
|
109
|
+
|
|
110
|
+
def _enclosed(self, i):
|
|
111
|
+
"""Cells not owned by i and not on i's trail that cannot reach the border
|
|
112
|
+
without crossing i's territory or trail."""
|
|
113
|
+
W, H = self.W, self.H
|
|
114
|
+
owner, trail = self.owner, self.trail
|
|
115
|
+
wall = bytearray(W * H)
|
|
116
|
+
for k in range(W * H):
|
|
117
|
+
if owner[k] == i or trail[k] == i:
|
|
118
|
+
wall[k] = 1
|
|
119
|
+
seen = bytearray(W * H)
|
|
120
|
+
stack = []
|
|
121
|
+
for x in range(W):
|
|
122
|
+
for k in (x, (H - 1) * W + x):
|
|
123
|
+
if not wall[k] and not seen[k]:
|
|
124
|
+
seen[k] = 1
|
|
125
|
+
stack.append(k)
|
|
126
|
+
for y in range(H):
|
|
127
|
+
for k in (y * W, y * W + W - 1):
|
|
128
|
+
if not wall[k] and not seen[k]:
|
|
129
|
+
seen[k] = 1
|
|
130
|
+
stack.append(k)
|
|
131
|
+
while stack:
|
|
132
|
+
k = stack.pop()
|
|
133
|
+
x = k % W
|
|
134
|
+
for nk, ok in ((k - W, k >= W), (k + W, k < W * (H - 1)),
|
|
135
|
+
(k - 1, x > 0), (k + 1, x < W - 1)):
|
|
136
|
+
if ok and not wall[nk] and not seen[nk]:
|
|
137
|
+
seen[nk] = 1
|
|
138
|
+
stack.append(nk)
|
|
139
|
+
return [k for k in range(W * H) if not wall[k] and not seen[k]]
|
|
140
|
+
|
|
141
|
+
def step(self, moves):
|
|
142
|
+
"""Advance one turn. moves[i] is 'U'/'D'/'L'/'R' or None (keep direction).
|
|
143
|
+
Returns a frame dict describing what changed (used for replays)."""
|
|
144
|
+
W, H, N = self.W, self.H, self.N
|
|
145
|
+
dirs, new = [], []
|
|
146
|
+
dead = [None] * N # (reason, other_player)
|
|
147
|
+
|
|
148
|
+
# 1. intended moves; reversing or invalid input keeps the current direction
|
|
149
|
+
for i in range(N):
|
|
150
|
+
d = self.dir[i]
|
|
151
|
+
m = moves[i]
|
|
152
|
+
if self.out[i]:
|
|
153
|
+
dirs.append(d)
|
|
154
|
+
new.append(None)
|
|
155
|
+
continue
|
|
156
|
+
if m in DIRS and m != OPPOSITE[d]:
|
|
157
|
+
d = m
|
|
158
|
+
dirs.append(d)
|
|
159
|
+
x, y = self.pos[i]
|
|
160
|
+
dx, dy = DIRS[d]
|
|
161
|
+
nx, ny = x + dx, y + dy
|
|
162
|
+
if 0 <= nx < W and 0 <= ny < H:
|
|
163
|
+
new.append(ny * W + nx)
|
|
164
|
+
else:
|
|
165
|
+
new.append(None)
|
|
166
|
+
dead[i] = ('wall', None)
|
|
167
|
+
|
|
168
|
+
# 2. head-on collisions (same target cell, or swapping cells)
|
|
169
|
+
cur = [py * W + px for px, py in self.pos]
|
|
170
|
+
for i in range(N):
|
|
171
|
+
for j in range(i + 1, N):
|
|
172
|
+
if new[i] is None or new[j] is None:
|
|
173
|
+
continue
|
|
174
|
+
if new[i] == new[j] or (new[i] == cur[j] and new[j] == cur[i]):
|
|
175
|
+
dead[i] = dead[i] or ('head', j)
|
|
176
|
+
dead[j] = dead[j] or ('head', i)
|
|
177
|
+
|
|
178
|
+
# 3. trail cuts: entering a trail cell kills the trail's owner
|
|
179
|
+
for i in range(N):
|
|
180
|
+
if new[i] is None:
|
|
181
|
+
continue
|
|
182
|
+
c = self.trail[new[i]]
|
|
183
|
+
if c != -1 and dead[c] is None:
|
|
184
|
+
dead[c] = ('self', i) if c == i else ('cut', i)
|
|
185
|
+
|
|
186
|
+
frame = {'events': [], 'owner': [], 'trail_clear': [], 'trail_add': []}
|
|
187
|
+
|
|
188
|
+
# 4. deaths: drop trail, teleport to spawn, keep territory
|
|
189
|
+
for i in range(N):
|
|
190
|
+
if dead[i] is None:
|
|
191
|
+
continue
|
|
192
|
+
for k in self.trails[i]:
|
|
193
|
+
self.trail[k] = -1
|
|
194
|
+
self.trails[i] = []
|
|
195
|
+
self.pos[i] = self.spawns[i]
|
|
196
|
+
self.dir[i] = self.init_dirs[i]
|
|
197
|
+
self.deaths[i] += 1
|
|
198
|
+
reason, by = dead[i]
|
|
199
|
+
if reason == 'cut':
|
|
200
|
+
self.kills[by] += 1
|
|
201
|
+
frame['events'].append({'type': 'death', 'player': i, 'reason': reason, 'by': by})
|
|
202
|
+
frame['trail_clear'].append(i)
|
|
203
|
+
|
|
204
|
+
# 5. survivors move
|
|
205
|
+
capturers = []
|
|
206
|
+
for i in range(N):
|
|
207
|
+
if dead[i] is not None or self.out[i]:
|
|
208
|
+
continue
|
|
209
|
+
k = new[i]
|
|
210
|
+
self.dir[i] = dirs[i]
|
|
211
|
+
self.pos[i] = (k % W, k // W)
|
|
212
|
+
if self.owner[k] == i:
|
|
213
|
+
if self.trails[i]:
|
|
214
|
+
capturers.append(i)
|
|
215
|
+
else:
|
|
216
|
+
self.trails[i].append(k)
|
|
217
|
+
self.trail[k] = i
|
|
218
|
+
frame['trail_add'].append([i, k])
|
|
219
|
+
|
|
220
|
+
# 6. captures, computed on the pre-capture board and applied together
|
|
221
|
+
if capturers:
|
|
222
|
+
enclosed = {i: self._enclosed(i) for i in capturers}
|
|
223
|
+
trail_cells = {}
|
|
224
|
+
for i in capturers:
|
|
225
|
+
for k in self.trails[i]:
|
|
226
|
+
trail_cells[k] = i
|
|
227
|
+
claims = {}
|
|
228
|
+
for i in capturers:
|
|
229
|
+
for k in enclosed[i]:
|
|
230
|
+
claims.setdefault(k, []).append(i)
|
|
231
|
+
gained = {i: 0 for i in capturers}
|
|
232
|
+
|
|
233
|
+
def take(k, i):
|
|
234
|
+
if self.protected[k] not in (-1, i) or self.owner[k] == i:
|
|
235
|
+
return
|
|
236
|
+
self.owner[k] = i
|
|
237
|
+
gained[i] += 1
|
|
238
|
+
frame['owner'].append([k, i])
|
|
239
|
+
|
|
240
|
+
for k, i in trail_cells.items():
|
|
241
|
+
take(k, i)
|
|
242
|
+
for k, cl in claims.items():
|
|
243
|
+
if len(cl) == 1 and k not in trail_cells:
|
|
244
|
+
take(k, cl[0])
|
|
245
|
+
for i in capturers:
|
|
246
|
+
for k in self.trails[i]:
|
|
247
|
+
self.trail[k] = -1
|
|
248
|
+
self.trails[i] = []
|
|
249
|
+
frame['trail_clear'].append(i)
|
|
250
|
+
frame['events'].append({'type': 'capture', 'player': i, 'cells': gained[i]})
|
|
251
|
+
|
|
252
|
+
self.turn += 1
|
|
253
|
+
frame['pos'] = [[x, y, d] for (x, y), d in zip(self.pos, self.dir)]
|
|
254
|
+
return frame
|
|
255
|
+
|
|
256
|
+
def state_text(self, turn):
|
|
257
|
+
"""Per-turn message sent to bots (see RULES.md)."""
|
|
258
|
+
areas = self.areas()
|
|
259
|
+
lines = ['TURN %d' % turn]
|
|
260
|
+
for i in range(self.N):
|
|
261
|
+
x, y = self.pos[i]
|
|
262
|
+
lines.append('P %d %d %d %s %d %d %d' % (
|
|
263
|
+
i, x, y, self.dir[i], len(self.trails[i]), areas[i], self.deaths[i]))
|
|
264
|
+
W = self.W
|
|
265
|
+
ch = '.0123456789'
|
|
266
|
+
lines.append('OWNER')
|
|
267
|
+
row = ''.join(ch[o + 1] for o in self.owner)
|
|
268
|
+
lines.extend(row[y * W:(y + 1) * W] for y in range(self.H))
|
|
269
|
+
lines.append('TRAIL')
|
|
270
|
+
row = ''.join(ch[o + 1] for o in self.trail)
|
|
271
|
+
lines.extend(row[y * W:(y + 1) * W] for y in range(self.H))
|
|
272
|
+
lines.append('END')
|
|
273
|
+
return '\n'.join(lines) + '\n'
|