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,1132 @@
|
|
|
1
|
+
#include "tank.hpp"
|
|
2
|
+
#include <algorithm>
|
|
3
|
+
#include <cmath>
|
|
4
|
+
#include <cstdint>
|
|
5
|
+
#include <cstring>
|
|
6
|
+
|
|
7
|
+
using namespace tank;
|
|
8
|
+
|
|
9
|
+
namespace {
|
|
10
|
+
|
|
11
|
+
constexpr int INF = 1000000;
|
|
12
|
+
|
|
13
|
+
struct MapCache {
|
|
14
|
+
// For each tank 0..5, cost in sub-steps to attack the opponent's base from cell c
|
|
15
|
+
int dist_atk[6][CELLS];
|
|
16
|
+
// For each tank 0..5, cost in sub-steps from tank's current pos to cell c
|
|
17
|
+
int dist_from_tank[6][CELLS];
|
|
18
|
+
// For each supply 0..2, cost in steps (brick=3, empty=1) to cell c
|
|
19
|
+
int dist_supply[MAX_SUPPLY][CELLS];
|
|
20
|
+
// Is cell part of seat's base protective ring/wall?
|
|
21
|
+
bool is_base_shield[2][CELLS];
|
|
22
|
+
// Shooting post info for attacking base of seat (0 or 1):
|
|
23
|
+
int base_ray_dist[2][CELLS];
|
|
24
|
+
int base_ray_dir[2][CELLS]; // direction from c toward base[seat]
|
|
25
|
+
int base_ray_bricks[2][CELLS];
|
|
26
|
+
int base_ray_steels[2][CELLS];
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
static MapCache G;
|
|
30
|
+
static int last_pos_hist[6][6];
|
|
31
|
+
static int last_op_base_hp[2] = {24, 24};
|
|
32
|
+
static int no_dmg_turns[2] = {0, 0};
|
|
33
|
+
static int hist_turn = -1;
|
|
34
|
+
|
|
35
|
+
inline int manhattan(int c1, int c2) {
|
|
36
|
+
return std::abs(cell_x(c1) - cell_x(c2)) + std::abs(cell_y(c1) - cell_y(c2));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
inline bool is_enemy_spawn_soon(const State& s, int seat, int cell) {
|
|
40
|
+
int op = 1 - seat;
|
|
41
|
+
for (int t = 0; t < 3; ++t) {
|
|
42
|
+
int i = tank_id(op, t);
|
|
43
|
+
if (s.spawn[i] == cell && !s.tank[i].alive && respawn_in(s, i) <= 1) {
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
inline bool is_ally_spawn_soon(const State& s, int seat, int self_id, int cell) {
|
|
51
|
+
for (int t = 0; t < 3; ++t) {
|
|
52
|
+
int i = tank_id(seat, t);
|
|
53
|
+
if (i != self_id && s.spawn[i] == cell && !s.tank[i].alive && respawn_in(s, i) <= 1) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
void precompute(const State& s) {
|
|
61
|
+
std::memset(&G, 0, sizeof(G));
|
|
62
|
+
const int n = s.P.size;
|
|
63
|
+
const int R = s.P.ring;
|
|
64
|
+
|
|
65
|
+
for (int seat = 0; seat < 2; ++seat) {
|
|
66
|
+
int bpos = s.base_pos[seat];
|
|
67
|
+
int bx = cell_x(bpos), by = cell_y(bpos);
|
|
68
|
+
for (int y = std::max(0, by - R); y <= std::min(n - 1, by + R); ++y) {
|
|
69
|
+
for (int x = std::max(0, bx - R); x <= std::min(n - 1, bx + R); ++x) {
|
|
70
|
+
int c = cell_of(x, y);
|
|
71
|
+
if (c != bpos) G.is_base_shield[seat][c] = true;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
for (int d = 0; d < 4; ++d) {
|
|
75
|
+
int bricks = 0, steels = 0;
|
|
76
|
+
int to_base_dir = d ^ 1;
|
|
77
|
+
for (int k = 1;; ++k) {
|
|
78
|
+
int c = bpos + k * DIR_DELTA[d];
|
|
79
|
+
uint8_t g = s.grid[c];
|
|
80
|
+
if (g == OUTSIDE || g == BASE0 || g == BASE1) break;
|
|
81
|
+
if (k <= 3 && (g == BRICK || g == STEEL)) {
|
|
82
|
+
G.is_base_shield[seat][c] = true;
|
|
83
|
+
}
|
|
84
|
+
G.base_ray_dist[seat][c] = k;
|
|
85
|
+
G.base_ray_dir[seat][c] = to_base_dir;
|
|
86
|
+
G.base_ray_bricks[seat][c] = bricks;
|
|
87
|
+
G.base_ray_steels[seat][c] = steels;
|
|
88
|
+
if (g == BRICK) bricks++;
|
|
89
|
+
else if (g == STEEL) steels++;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Supply distances
|
|
95
|
+
for (int k = 0; k < s.n_supply; ++k) {
|
|
96
|
+
for (int c = 0; c < CELLS; ++c) G.dist_supply[k][c] = INF;
|
|
97
|
+
int src = s.supply[k];
|
|
98
|
+
int q[CELLS * 4];
|
|
99
|
+
int head = 0, tail = 0;
|
|
100
|
+
G.dist_supply[k][src] = 0;
|
|
101
|
+
q[tail++] = src;
|
|
102
|
+
while (head < tail) {
|
|
103
|
+
int u = q[head++];
|
|
104
|
+
int du = G.dist_supply[k][u];
|
|
105
|
+
for (int d = 0; d < 4; ++d) {
|
|
106
|
+
int v = u + DIR_DELTA[d];
|
|
107
|
+
uint8_t g = s.grid[v];
|
|
108
|
+
int w = (g == EMPTY) ? 1 : (g == BRICK ? 3 : INF);
|
|
109
|
+
if (w < INF && du + w < G.dist_supply[k][v]) {
|
|
110
|
+
G.dist_supply[k][v] = du + w;
|
|
111
|
+
q[tail++] = v;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Per-tank Dijkstra for attacking enemy base and distance from tank
|
|
118
|
+
for (int i = 0; i < 6; ++i) {
|
|
119
|
+
int seat = seat_of(i);
|
|
120
|
+
int op = 1 - seat;
|
|
121
|
+
int spd = s.P.types[type_of(i)].speed;
|
|
122
|
+
int rl = reload_turns(s, i);
|
|
123
|
+
bool can_steel = (s.tank[i].level >= 2 && s.P.level2_steel);
|
|
124
|
+
int move_cost = (spd >= 2) ? 1 : 2;
|
|
125
|
+
int wall_cost = move_cost + 2 + 2 * rl;
|
|
126
|
+
|
|
127
|
+
for (int c = 0; c < CELLS; ++c) {
|
|
128
|
+
G.dist_atk[i][c] = INF;
|
|
129
|
+
G.dist_from_tank[i][c] = INF;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// 1. Seed shooting posts for enemy base `op`
|
|
133
|
+
int bpos = s.base_pos[op];
|
|
134
|
+
int q[CELLS * 8];
|
|
135
|
+
int head = 0, tail = 0;
|
|
136
|
+
|
|
137
|
+
for (int d = 0; d < 4; ++d) {
|
|
138
|
+
for (int k = 1;; ++k) {
|
|
139
|
+
int c = bpos + k * DIR_DELTA[d];
|
|
140
|
+
uint8_t g = s.grid[c];
|
|
141
|
+
if (g == OUTSIDE || g == BASE0 || g == BASE1) break;
|
|
142
|
+
int steels_b = G.base_ray_steels[op][c];
|
|
143
|
+
int bricks_b = G.base_ray_bricks[op][c];
|
|
144
|
+
if (steels_b > 0 && !can_steel) break;
|
|
145
|
+
if (g == WATER) continue;
|
|
146
|
+
if (g == STEEL && !can_steel) continue;
|
|
147
|
+
|
|
148
|
+
int walls_b = bricks_b + steels_b;
|
|
149
|
+
int shot_interval = 2 + 2 * rl + (k > 2 ? (k - 1) : 0);
|
|
150
|
+
int post_cost = walls_b * shot_interval;
|
|
151
|
+
if (k == 1) post_cost += 0;
|
|
152
|
+
else if (k == 2) post_cost += 1;
|
|
153
|
+
else post_cost += 2 + (k - 2) * 3;
|
|
154
|
+
|
|
155
|
+
// Penalty if c is on or right in front of an enemy spawn point
|
|
156
|
+
for (int t = 0; t < 3; ++t) {
|
|
157
|
+
int sp = s.spawn[tank_id(op, t)];
|
|
158
|
+
if (sp == c) post_cost += 4;
|
|
159
|
+
else if (c == bpos + DIR_DELTA[d] && sp == bpos + 2 * DIR_DELTA[d]) {
|
|
160
|
+
post_cost += 2; // Standing at (7,1)/(7,13) right in front of center spawn
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (g == BRICK || g == STEEL) {
|
|
164
|
+
post_cost += 2 + 2 * rl;
|
|
165
|
+
}
|
|
166
|
+
if (post_cost < G.dist_atk[i][c]) {
|
|
167
|
+
G.dist_atk[i][c] = post_cost;
|
|
168
|
+
q[tail++] = c;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
while (head < tail) {
|
|
174
|
+
int u = q[head++];
|
|
175
|
+
int du = G.dist_atk[i][u];
|
|
176
|
+
for (int d = 0; d < 4; ++d) {
|
|
177
|
+
int v = u + DIR_DELTA[d];
|
|
178
|
+
uint8_t g = s.grid[v];
|
|
179
|
+
if (g == OUTSIDE || g == WATER || g == BASE0 || g == BASE1) continue;
|
|
180
|
+
if (g == STEEL && !can_steel) continue;
|
|
181
|
+
if (G.is_base_shield[seat][v] && (g == BRICK || g == STEEL)) continue;
|
|
182
|
+
int step_c = (g == EMPTY) ? move_cost : wall_cost;
|
|
183
|
+
if (du + step_c < G.dist_atk[i][v]) {
|
|
184
|
+
G.dist_atk[i][v] = du + step_c;
|
|
185
|
+
q[tail++] = v;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// 2. Distance from tank's current position (or spawn if dead)
|
|
191
|
+
int start_c = s.tank[i].alive ? s.tank[i].pos : s.spawn[i];
|
|
192
|
+
head = tail = 0;
|
|
193
|
+
G.dist_from_tank[i][start_c] = 0;
|
|
194
|
+
q[tail++] = start_c;
|
|
195
|
+
while (head < tail) {
|
|
196
|
+
int u = q[head++];
|
|
197
|
+
int du = G.dist_from_tank[i][u];
|
|
198
|
+
for (int d = 0; d < 4; ++d) {
|
|
199
|
+
int v = u + DIR_DELTA[d];
|
|
200
|
+
uint8_t g = s.grid[v];
|
|
201
|
+
if (g == OUTSIDE || g == WATER || g == BASE0 || g == BASE1) continue;
|
|
202
|
+
if (g == STEEL && !can_steel) continue;
|
|
203
|
+
if (G.is_base_shield[seat][v] && (g == BRICK || g == STEEL)) continue;
|
|
204
|
+
int step_c = (g == EMPTY) ? move_cost : wall_cost;
|
|
205
|
+
if (du + step_c < G.dist_from_tank[i][v]) {
|
|
206
|
+
G.dist_from_tank[i][v] = du + step_c;
|
|
207
|
+
q[tail++] = v;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
struct RayHit {
|
|
215
|
+
int kind; // 0=none/outside, 1=brick, 2=steel, 3=my_base, 4=op_base, 5=ally_tank, 6=enemy_tank, 7=enemy_bullet
|
|
216
|
+
int dist;
|
|
217
|
+
int target_id;
|
|
218
|
+
int cell;
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
RayHit trace_shot(const State& s, int seat, int self_id, int pos, int dir) {
|
|
222
|
+
int opp_dir = dir ^ 1;
|
|
223
|
+
for (int step = 1;; ++step) {
|
|
224
|
+
int c = pos + step * DIR_DELTA[dir];
|
|
225
|
+
for (int k = 0; k < s.n_bullets; ++k) {
|
|
226
|
+
if (s.bullet[k].pos == c && s.bullet[k].dir == opp_dir) {
|
|
227
|
+
return RayHit{7, step, k, c};
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
uint8_t g = s.grid[c];
|
|
231
|
+
if (g == OUTSIDE) return RayHit{0, step, -1, c};
|
|
232
|
+
if (g == BASE0 + seat) return RayHit{3, step, seat, c};
|
|
233
|
+
if (g == BASE0 + (1 - seat)) return RayHit{4, step, 1 - seat, c};
|
|
234
|
+
if (g == BRICK) return RayHit{1, step, -1, c};
|
|
235
|
+
if (g == STEEL) return RayHit{2, step, -1, c};
|
|
236
|
+
if (g == EMPTY) {
|
|
237
|
+
int tid = tank_at(s, c);
|
|
238
|
+
if (tid >= 0 && tid != self_id) {
|
|
239
|
+
if (seat_of(tid) == seat) return RayHit{5, step, tid, c};
|
|
240
|
+
else return RayHit{6, step, tid, c};
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
struct MoveSafety {
|
|
247
|
+
int p1, p2;
|
|
248
|
+
int existing_bullet_hits;
|
|
249
|
+
int potential_enemy_hits;
|
|
250
|
+
int cancels_enemy_bullet;
|
|
251
|
+
int cancels_base_bullet;
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
MoveSafety check_cmd_safety(const State& s, int i, Cmd c) {
|
|
255
|
+
MoveSafety ms{};
|
|
256
|
+
const Tank& t = s.tank[i];
|
|
257
|
+
int seat = seat_of(i);
|
|
258
|
+
int op = 1 - seat;
|
|
259
|
+
int pos = t.pos;
|
|
260
|
+
ms.p1 = pos;
|
|
261
|
+
ms.p2 = pos;
|
|
262
|
+
|
|
263
|
+
bool fired = false;
|
|
264
|
+
int fire_dir = -1;
|
|
265
|
+
if (is_move(c)) {
|
|
266
|
+
int d1 = c - 1;
|
|
267
|
+
int n1 = pos + DIR_DELTA[d1];
|
|
268
|
+
if (s.grid[n1] == EMPTY && tank_at(s, n1) < 0) ms.p1 = n1;
|
|
269
|
+
ms.p2 = ms.p1;
|
|
270
|
+
} else if (is_move2(c)) {
|
|
271
|
+
int d1 = (c - 9) >> 2, d2 = (c - 9) & 3;
|
|
272
|
+
int n1 = pos + DIR_DELTA[d1];
|
|
273
|
+
if (s.grid[n1] == EMPTY && tank_at(s, n1) < 0) ms.p1 = n1;
|
|
274
|
+
int n2 = ms.p1 + DIR_DELTA[d2];
|
|
275
|
+
if (s.grid[n2] == EMPTY && (n2 == pos || tank_at(s, n2) < 0)) ms.p2 = n2;
|
|
276
|
+
} else if (is_fire(c) && can_fire(s, i)) {
|
|
277
|
+
fired = true;
|
|
278
|
+
fire_dir = c - 5;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
for (int k = 0; k < s.n_bullets; ++k) {
|
|
282
|
+
const Bullet& b = s.bullet[k];
|
|
283
|
+
int b1 = b.pos + DIR_DELTA[b.dir];
|
|
284
|
+
if (fired && b.dir == (fire_dir ^ 1)) {
|
|
285
|
+
int f1 = pos + DIR_DELTA[fire_dir];
|
|
286
|
+
if (b.pos == f1 || b1 == f1) {
|
|
287
|
+
ms.cancels_enemy_bullet++;
|
|
288
|
+
if (G.base_ray_dist[seat][b.pos] > 0 && G.base_ray_dir[seat][b.pos] == b.dir) {
|
|
289
|
+
ms.cancels_base_bullet++;
|
|
290
|
+
}
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (ms.p1 != pos && b1 == pos && ms.p1 == b.pos) {
|
|
295
|
+
ms.existing_bullet_hits++;
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
if (b1 == ms.p1 && s.grid[b1] == EMPTY) {
|
|
299
|
+
ms.existing_bullet_hits++;
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
uint8_t g1 = s.grid[b1];
|
|
303
|
+
if (g1 != EMPTY && g1 != WATER) continue;
|
|
304
|
+
int v1 = tank_at(s, b1);
|
|
305
|
+
if (v1 >= 0 && v1 != i) continue;
|
|
306
|
+
|
|
307
|
+
int b2 = b1 + DIR_DELTA[b.dir];
|
|
308
|
+
if (fired && b.dir == (fire_dir ^ 1)) {
|
|
309
|
+
int f1 = pos + DIR_DELTA[fire_dir];
|
|
310
|
+
if (s.grid[f1] == EMPTY || s.grid[f1] == WATER) {
|
|
311
|
+
int f2 = f1 + DIR_DELTA[fire_dir];
|
|
312
|
+
if (b1 == f2 || b2 == f2) {
|
|
313
|
+
ms.cancels_enemy_bullet++;
|
|
314
|
+
if (G.base_ray_dist[seat][b.pos] > 0 && G.base_ray_dir[seat][b.pos] == b.dir) {
|
|
315
|
+
ms.cancels_base_bullet++;
|
|
316
|
+
}
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
if (ms.p2 != ms.p1 && b2 == ms.p1 && ms.p2 == b1) {
|
|
322
|
+
ms.existing_bullet_hits++;
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
if (b2 == ms.p2 && s.grid[b2] == EMPTY) {
|
|
326
|
+
ms.existing_bullet_hits++;
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
for (int t_idx = 0; t_idx < 3; ++t_idx) {
|
|
332
|
+
int e = tank_id(op, t_idx);
|
|
333
|
+
if (!s.tank[e].alive || !can_fire(s, e)) continue;
|
|
334
|
+
int epos = s.tank[e].pos;
|
|
335
|
+
for (int ed = 0; ed < 4; ++ed) {
|
|
336
|
+
int eb1 = epos + DIR_DELTA[ed];
|
|
337
|
+
if (fired && ed == (fire_dir ^ 1)) {
|
|
338
|
+
int f1 = pos + DIR_DELTA[fire_dir];
|
|
339
|
+
if (epos == f1 || eb1 == f1) continue;
|
|
340
|
+
}
|
|
341
|
+
if (eb1 == ms.p1 && s.grid[eb1] == EMPTY) {
|
|
342
|
+
ms.potential_enemy_hits++;
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
uint8_t g1 = s.grid[eb1];
|
|
346
|
+
if (g1 != EMPTY && g1 != WATER) continue;
|
|
347
|
+
int v1 = tank_at(s, eb1);
|
|
348
|
+
if (v1 >= 0 && v1 != i) continue;
|
|
349
|
+
|
|
350
|
+
int eb2 = eb1 + DIR_DELTA[ed];
|
|
351
|
+
if (fired && ed == (fire_dir ^ 1)) {
|
|
352
|
+
int f1 = pos + DIR_DELTA[fire_dir];
|
|
353
|
+
if (s.grid[f1] == EMPTY || s.grid[f1] == WATER) {
|
|
354
|
+
int f2 = f1 + DIR_DELTA[fire_dir];
|
|
355
|
+
if (eb1 == f2 || eb2 == f2) continue;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
if (eb2 == ms.p2 && s.grid[eb2] == EMPTY) {
|
|
359
|
+
ms.potential_enemy_hits++;
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return ms;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Is enemy tank `e` currently a serious threat to `seat`'s base?
|
|
369
|
+
int enemy_base_threat_level(const State& s, int seat, int e) {
|
|
370
|
+
if (!s.tank[e].alive) return 0;
|
|
371
|
+
int epos = s.tank[e].pos;
|
|
372
|
+
// Direct line of fire to our base with 0 or 1 brick!
|
|
373
|
+
if (G.base_ray_dist[seat][epos] > 0) {
|
|
374
|
+
int steels = G.base_ray_steels[seat][epos];
|
|
375
|
+
int bricks = G.base_ray_bricks[seat][epos];
|
|
376
|
+
bool can_st = (s.tank[e].level >= 2 && s.P.level2_steel);
|
|
377
|
+
if (steels == 0 || can_st) {
|
|
378
|
+
if (bricks == 0 && G.base_ray_dist[seat][epos] <= 5) return 3; // Actively hitting our base!
|
|
379
|
+
if (bricks <= 1 && G.base_ray_dist[seat][epos] <= 4) return 2;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
int eatk = G.dist_atk[e][epos];
|
|
383
|
+
if (eatk <= 4) return 3;
|
|
384
|
+
if (eatk <= 8 || manhattan(epos, s.base_pos[seat]) <= 4) return 2;
|
|
385
|
+
if (eatk <= 12) return 1;
|
|
386
|
+
return 0;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
double score_single_cmd(const State& s, int i, Cmd c, bool use_global_maps = true) {
|
|
390
|
+
const Tank& t = s.tank[i];
|
|
391
|
+
if (!t.alive) return 0.0;
|
|
392
|
+
int seat = seat_of(i);
|
|
393
|
+
int op = 1 - seat;
|
|
394
|
+
int pos = t.pos;
|
|
395
|
+
|
|
396
|
+
MoveSafety ms = check_cmd_safety(s, i, c);
|
|
397
|
+
double score = 0.0;
|
|
398
|
+
|
|
399
|
+
// 1. Survival against existing bullets and telefrags
|
|
400
|
+
if (ms.existing_bullet_hits > 0) {
|
|
401
|
+
if (ms.existing_bullet_hits >= t.hp) {
|
|
402
|
+
score -= 5000.0 + 1500.0 * t.level;
|
|
403
|
+
} else {
|
|
404
|
+
score -= 1800.0 * ms.existing_bullet_hits;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
if (ms.potential_enemy_hits > 0) {
|
|
408
|
+
if (ms.potential_enemy_hits >= t.hp) {
|
|
409
|
+
score -= 1450.0 + 500.0 * t.level;
|
|
410
|
+
} else {
|
|
411
|
+
score -= 550.0 * ms.potential_enemy_hits;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
if (ms.cancels_enemy_bullet > 0) {
|
|
415
|
+
score += 180.0 * ms.cancels_enemy_bullet;
|
|
416
|
+
}
|
|
417
|
+
if (ms.cancels_base_bullet > 0) {
|
|
418
|
+
score += 1100.0 * ms.cancels_base_bullet;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
if (is_enemy_spawn_soon(s, seat, ms.p2)) {
|
|
422
|
+
score -= 8000.0;
|
|
423
|
+
}
|
|
424
|
+
if (is_ally_spawn_soon(s, seat, i, ms.p2)) {
|
|
425
|
+
score -= 900.0;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Check if tank `i` has been stuck in the same cell for >= 3 turns
|
|
429
|
+
bool is_stuck = (last_pos_hist[i][0] == pos && last_pos_hist[i][1] == pos && last_pos_hist[i][2] == pos);
|
|
430
|
+
|
|
431
|
+
// 2. Firing evaluation
|
|
432
|
+
if (is_fire(c)) {
|
|
433
|
+
if (!can_fire(s, i)) return -1e9;
|
|
434
|
+
int d = c - 5;
|
|
435
|
+
RayHit rh = trace_shot(s, seat, i, pos, d);
|
|
436
|
+
|
|
437
|
+
if (rh.kind == 3) {
|
|
438
|
+
return -100000.0; // Never shoot own base!
|
|
439
|
+
}
|
|
440
|
+
if (rh.kind == 5) {
|
|
441
|
+
if (rh.dist <= 4) score -= 2500.0;
|
|
442
|
+
else score -= 400.0;
|
|
443
|
+
} else if (rh.kind == 1) {
|
|
444
|
+
if (G.is_base_shield[seat][rh.cell]) {
|
|
445
|
+
score -= 6000.0;
|
|
446
|
+
} else if (G.is_base_shield[op][rh.cell] || (G.base_ray_dist[op][pos] > 0 && G.base_ray_dir[op][pos] == d)) {
|
|
447
|
+
if (rh.dist <= 2) score += 550.0;
|
|
448
|
+
else if (rh.dist <= 4) score += 300.0;
|
|
449
|
+
else score += 60.0;
|
|
450
|
+
} else if (rh.dist <= 2) {
|
|
451
|
+
if (use_global_maps && G.dist_atk[i][rh.cell] < G.dist_atk[i][pos]) {
|
|
452
|
+
score += 320.0;
|
|
453
|
+
} else {
|
|
454
|
+
score += 65.0;
|
|
455
|
+
}
|
|
456
|
+
} else {
|
|
457
|
+
score -= 140.0;
|
|
458
|
+
}
|
|
459
|
+
} else if (rh.kind == 2) {
|
|
460
|
+
if (t.level >= 2 && s.P.level2_steel) {
|
|
461
|
+
if (G.is_base_shield[seat][rh.cell]) {
|
|
462
|
+
score -= 6000.0;
|
|
463
|
+
} else if (rh.dist <= 3 && (!use_global_maps || G.dist_atk[i][rh.cell] < G.dist_atk[i][pos] || G.base_ray_dist[op][pos] > 0)) {
|
|
464
|
+
score += 360.0;
|
|
465
|
+
} else {
|
|
466
|
+
score -= 80.0;
|
|
467
|
+
}
|
|
468
|
+
} else {
|
|
469
|
+
score -= 200.0;
|
|
470
|
+
}
|
|
471
|
+
} else if (rh.kind == 4) {
|
|
472
|
+
int next_c = pos + DIR_DELTA[d];
|
|
473
|
+
bool can_walk_closer = (s.grid[next_c] == EMPTY && tank_at(s, next_c) < 0);
|
|
474
|
+
if (rh.dist <= 2) {
|
|
475
|
+
score += 1250.0;
|
|
476
|
+
} else if (rh.dist <= 4) {
|
|
477
|
+
score += can_walk_closer ? 620.0 : 950.0;
|
|
478
|
+
} else {
|
|
479
|
+
score += can_walk_closer ? 80.0 : 600.0;
|
|
480
|
+
}
|
|
481
|
+
} else if (rh.kind == 6) {
|
|
482
|
+
int enemy_id = rh.target_id;
|
|
483
|
+
const Tank& et = s.tank[enemy_id];
|
|
484
|
+
bool enemy_can_fire = can_fire(s, enemy_id);
|
|
485
|
+
int next_c = pos + DIR_DELTA[d];
|
|
486
|
+
bool can_advance = (s.grid[next_c] == EMPTY && tank_at(s, next_c) < 0);
|
|
487
|
+
|
|
488
|
+
if (rh.dist == 1) {
|
|
489
|
+
score += enemy_can_fire ? 700.0 : 1150.0;
|
|
490
|
+
if (et.hp == 1) score += 300.0;
|
|
491
|
+
} else if (rh.dist == 2) {
|
|
492
|
+
score += enemy_can_fire ? 550.0 : 950.0;
|
|
493
|
+
if (et.hp == 1) score += 200.0;
|
|
494
|
+
} else if (rh.dist == 3) {
|
|
495
|
+
// At dist 3, bullet takes 2 turns. If we are stuck or chasing a base attacker, advance instead!
|
|
496
|
+
if (is_stuck && can_advance) {
|
|
497
|
+
score -= 150.0;
|
|
498
|
+
} else {
|
|
499
|
+
score += can_advance ? 40.0 : 180.0;
|
|
500
|
+
}
|
|
501
|
+
} else {
|
|
502
|
+
// At dist >= 4, shooting an enemy tank locks our gun and rarely hits (especially vs FIRE tank!)
|
|
503
|
+
if (can_advance) score -= 180.0;
|
|
504
|
+
else if (bullet_cap(s, i) > 1) score += 50.0;
|
|
505
|
+
else score -= 80.0;
|
|
506
|
+
}
|
|
507
|
+
} else if (rh.kind == 7) {
|
|
508
|
+
if (rh.dist <= 3) score += 220.0;
|
|
509
|
+
else score -= 60.0;
|
|
510
|
+
} else {
|
|
511
|
+
score -= 220.0;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// 3. Positional evaluation of resulting cell `ms.p2`
|
|
516
|
+
if (use_global_maps) {
|
|
517
|
+
// Check if this tank should intercept a dangerous enemy attacking our base!
|
|
518
|
+
int target_enemy = -1;
|
|
519
|
+
int best_threat = 0;
|
|
520
|
+
int best_edist = INF;
|
|
521
|
+
|
|
522
|
+
// Only assign defense if our base isn't winning a 1-turn base race right now
|
|
523
|
+
bool we_have_lethal_now = (s.base_hp[op] == 1 && G.dist_atk[i][pos] == 0);
|
|
524
|
+
if (!we_have_lethal_now && G.dist_atk[i][pos] > 2) {
|
|
525
|
+
for (int et_idx = 0; et_idx < 3; ++et_idx) {
|
|
526
|
+
int e = tank_id(op, et_idx);
|
|
527
|
+
int th = enemy_base_threat_level(s, seat, e);
|
|
528
|
+
if (th >= 2) {
|
|
529
|
+
int d_to_e = G.dist_from_tank[e][pos];
|
|
530
|
+
if (th > best_threat || (th == best_threat && d_to_e < best_edist)) {
|
|
531
|
+
best_threat = th;
|
|
532
|
+
best_edist = d_to_e;
|
|
533
|
+
target_enemy = e;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
if (target_enemy >= 0 && best_edist <= 18) {
|
|
540
|
+
// Use exact BFS distance G.dist_from_tank[target_enemy] to close in on the attacker!
|
|
541
|
+
int de_now = G.dist_from_tank[target_enemy][pos];
|
|
542
|
+
int de_next = G.dist_from_tank[target_enemy][ms.p2];
|
|
543
|
+
double w = (best_threat == 3) ? 95.0 : 55.0;
|
|
544
|
+
if (de_next < INF) {
|
|
545
|
+
score += (de_now - de_next) * w - de_next * 3.0;
|
|
546
|
+
}
|
|
547
|
+
// If adjacent brick blocks path to target_enemy, reward shooting that brick!
|
|
548
|
+
if (is_fire(c)) {
|
|
549
|
+
int fd = c - 5;
|
|
550
|
+
int adj = pos + DIR_DELTA[fd];
|
|
551
|
+
if (s.grid[adj] == BRICK && !G.is_base_shield[seat][adj] &&
|
|
552
|
+
G.dist_from_tank[target_enemy][adj] < de_now) {
|
|
553
|
+
score += 450.0;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
int d_now = G.dist_atk[i][pos];
|
|
559
|
+
int d_next = G.dist_atk[i][ms.p2];
|
|
560
|
+
double atk_weight = (target_enemy >= 0 && best_threat == 3 && best_edist <= 14) ? 15.0 : 45.0;
|
|
561
|
+
if (d_next < INF) {
|
|
562
|
+
score += (d_now - d_next) * atk_weight - d_next * 2.0;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// Star pickup / approach for tanks with level < 2
|
|
566
|
+
if (t.level < s.P.max_level && !(target_enemy >= 0 && best_threat == 3 && best_edist <= 10)) {
|
|
567
|
+
int next_turn = s.turn + 1;
|
|
568
|
+
int turns_to_spawn = INF;
|
|
569
|
+
if (next_turn <= s.P.supply_first) {
|
|
570
|
+
turns_to_spawn = s.P.supply_first - next_turn;
|
|
571
|
+
} else {
|
|
572
|
+
int rem = (next_turn - s.P.supply_first) % s.P.supply_period;
|
|
573
|
+
turns_to_spawn = (rem == 0) ? 0 : (s.P.supply_period - rem);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
for (int k = 0; k < s.n_supply; ++k) {
|
|
577
|
+
bool has_star = ((s.star_mask >> k) & 1) != 0;
|
|
578
|
+
int sup_cell = s.supply[k];
|
|
579
|
+
int ds_now = G.dist_supply[k][pos];
|
|
580
|
+
int ds_next = G.dist_supply[k][ms.p2];
|
|
581
|
+
if (has_star) {
|
|
582
|
+
if (ms.p2 == sup_cell) {
|
|
583
|
+
score += 950.0;
|
|
584
|
+
} else if (ds_next <= 8) {
|
|
585
|
+
bool closer_ally = false;
|
|
586
|
+
for (int a = 0; a < 3; ++a) {
|
|
587
|
+
int aid = tank_id(seat, a);
|
|
588
|
+
if (aid != i && s.tank[aid].alive && s.tank[aid].level < s.P.max_level) {
|
|
589
|
+
if (G.dist_supply[k][s.tank[aid].pos] < ds_now) closer_ally = true;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
if (!closer_ally) {
|
|
593
|
+
score += (ds_now - ds_next) * 55.0 + (10 - ds_next) * 8.0;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
} else if (turns_to_spawn <= 5 && ds_next <= 6) {
|
|
597
|
+
bool closer_ally = false;
|
|
598
|
+
for (int a = 0; a < 3; ++a) {
|
|
599
|
+
int aid = tank_id(seat, a);
|
|
600
|
+
if (aid != i && s.tank[aid].alive && s.tank[aid].level < s.P.max_level) {
|
|
601
|
+
if (G.dist_supply[k][s.tank[aid].pos] < ds_now) closer_ally = true;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
if (!closer_ally) {
|
|
605
|
+
if (turns_to_spawn == 0 && ms.p2 == sup_cell) {
|
|
606
|
+
score += 220.0;
|
|
607
|
+
} else {
|
|
608
|
+
score += (ds_now - ds_next) * 35.0;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// Anti-oscillation & anti-stuck penalty
|
|
616
|
+
if (is_stuck && ms.p2 == pos && !is_fire(c)) {
|
|
617
|
+
score -= 80.0;
|
|
618
|
+
}
|
|
619
|
+
for (int h = 0; h < 4; ++h) {
|
|
620
|
+
if (ms.p2 == last_pos_hist[i][h] && ms.p2 != pos) {
|
|
621
|
+
score -= 14.0;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
return score;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
Orders fast_rollout_orders(const State& s, int seat) {
|
|
630
|
+
Orders ord;
|
|
631
|
+
int chosen_p1[3] = {-1, -1, -1};
|
|
632
|
+
int chosen_p2[3] = {-1, -1, -1};
|
|
633
|
+
bool base_shot_taken = false;
|
|
634
|
+
int op = 1 - seat;
|
|
635
|
+
|
|
636
|
+
for (int type = 0; type < 3; ++type) {
|
|
637
|
+
int i = tank_id(seat, type);
|
|
638
|
+
if (!s.tank[i].alive) {
|
|
639
|
+
ord.cmd[type] = STAY;
|
|
640
|
+
continue;
|
|
641
|
+
}
|
|
642
|
+
Cmd list[N_CMDS];
|
|
643
|
+
int n = legal_commands(s, i, list);
|
|
644
|
+
double best_sc = -1e18;
|
|
645
|
+
Cmd best_c = STAY;
|
|
646
|
+
|
|
647
|
+
for (int k = 0; k < n; ++k) {
|
|
648
|
+
Cmd c = list[k];
|
|
649
|
+
double sc = score_single_cmd(s, i, c, true);
|
|
650
|
+
MoveSafety ms = check_cmd_safety(s, i, c);
|
|
651
|
+
|
|
652
|
+
for (int prev = 0; prev < type; ++prev) {
|
|
653
|
+
if (ms.p1 == chosen_p1[prev] || ms.p2 == chosen_p2[prev]) {
|
|
654
|
+
sc -= 500.0;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
if (is_fire(c) && base_shot_taken) {
|
|
658
|
+
int d = c - 5;
|
|
659
|
+
if (G.base_ray_dist[op][s.tank[i].pos] > 0 && G.base_ray_dir[op][s.tank[i].pos] == d &&
|
|
660
|
+
G.base_ray_bricks[op][s.tank[i].pos] == 0 && G.base_ray_steels[op][s.tank[i].pos] == 0) {
|
|
661
|
+
sc -= 900.0;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
if (sc > best_sc) {
|
|
666
|
+
best_sc = sc;
|
|
667
|
+
best_c = c;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
ord.cmd[type] = best_c;
|
|
671
|
+
MoveSafety ms = check_cmd_safety(s, i, best_c);
|
|
672
|
+
chosen_p1[type] = ms.p1;
|
|
673
|
+
chosen_p2[type] = ms.p2;
|
|
674
|
+
if (is_fire(best_c)) {
|
|
675
|
+
int d = best_c - 5;
|
|
676
|
+
int pos = s.tank[i].pos;
|
|
677
|
+
if (G.base_ray_dist[op][pos] == 1 || (G.base_ray_dist[op][pos] == 2 && G.base_ray_bricks[op][pos] == 0 && G.base_ray_steels[op][pos] == 0)) {
|
|
678
|
+
if (G.base_ray_dir[op][pos] == d) base_shot_taken = true;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
return ord;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
double evaluate_state(const State& s, int me) {
|
|
686
|
+
int op = 1 - me;
|
|
687
|
+
if (s.done) {
|
|
688
|
+
if (s.winner == me) {
|
|
689
|
+
return 1000000.0 + (300 - s.turn) * 500.0 + (s.base_hp[me] - s.base_hp[op]) * 2000.0 + (s.kills[me] - s.kills[op]) * 200.0;
|
|
690
|
+
} else if (s.winner == op) {
|
|
691
|
+
return -1000000.0 - (300 - s.turn) * 500.0 + (s.base_hp[me] - s.base_hp[op]) * 2000.0 + (s.kills[me] - s.kills[op]) * 200.0;
|
|
692
|
+
} else {
|
|
693
|
+
return 0.0;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
double val = 0.0;
|
|
698
|
+
|
|
699
|
+
// 1. Base HP
|
|
700
|
+
int my_bhp = s.base_hp[me];
|
|
701
|
+
int op_bhp = s.base_hp[op];
|
|
702
|
+
val += (my_bhp - op_bhp) * 4400.0;
|
|
703
|
+
if (my_bhp <= 6) val -= (7 - my_bhp) * 1200.0;
|
|
704
|
+
if (op_bhp <= 6) val += (7 - op_bhp) * 1200.0;
|
|
705
|
+
|
|
706
|
+
// 2. Kills and Stars Eaten
|
|
707
|
+
val += (s.kills[me] - s.kills[op]) * 850.0;
|
|
708
|
+
val += (s.stars_eaten[me] - s.stars_eaten[op]) * 750.0;
|
|
709
|
+
|
|
710
|
+
// 3. Base protective ring bricks
|
|
711
|
+
for (int seat = 0; seat < 2; ++seat) {
|
|
712
|
+
double sgn = (seat == me) ? 1.0 : -1.0;
|
|
713
|
+
int bpos = s.base_pos[seat];
|
|
714
|
+
for (int d = 0; d < 4; ++d) {
|
|
715
|
+
int c1 = bpos + DIR_DELTA[d];
|
|
716
|
+
if (s.grid[c1] == BRICK) val += sgn * 320.0;
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// 4. Bullets currently flying toward either base
|
|
721
|
+
for (int k = 0; k < s.n_bullets; ++k) {
|
|
722
|
+
const Bullet& b = s.bullet[k];
|
|
723
|
+
for (int seat = 0; seat < 2; ++seat) {
|
|
724
|
+
if (G.base_ray_dist[seat][b.pos] > 0 && G.base_ray_dir[seat][b.pos] == b.dir) {
|
|
725
|
+
int dist = G.base_ray_dist[seat][b.pos];
|
|
726
|
+
int bricks_in_way = G.base_ray_bricks[seat][b.pos];
|
|
727
|
+
int steels_in_way = G.base_ray_steels[seat][b.pos];
|
|
728
|
+
if (steels_in_way == 0 || b.steel) {
|
|
729
|
+
double sgn = (seat == me) ? -1.0 : 1.0;
|
|
730
|
+
if (bricks_in_way == 0 && dist <= 6) {
|
|
731
|
+
val += sgn * (1700.0 - dist * 180.0);
|
|
732
|
+
} else if (bricks_in_way == 1 && dist <= 4) {
|
|
733
|
+
val += sgn * 350.0;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
// 5. Tank alive status, HP, level, reload/ammo readiness, and bullet danger
|
|
741
|
+
int best_supply_dist[2][MAX_SUPPLY];
|
|
742
|
+
for (int k = 0; k < s.n_supply; ++k) {
|
|
743
|
+
best_supply_dist[0][k] = INF;
|
|
744
|
+
best_supply_dist[1][k] = INF;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
int best_atk_dist[2] = {INF, INF};
|
|
748
|
+
int second_atk_dist[2] = {INF, INF};
|
|
749
|
+
|
|
750
|
+
for (int i = 0; i < 6; ++i) {
|
|
751
|
+
int seat = seat_of(i);
|
|
752
|
+
double sgn = (seat == me) ? 1.0 : -1.0;
|
|
753
|
+
const Tank& t = s.tank[i];
|
|
754
|
+
|
|
755
|
+
if (!t.alive) {
|
|
756
|
+
int wait = respawn_in(s, i);
|
|
757
|
+
val -= sgn * (650.0 + wait * 220.0);
|
|
758
|
+
continue;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
double tank_val = 700.0 + t.hp * 480.0;
|
|
762
|
+
if (t.level == 1) tank_val += 1100.0;
|
|
763
|
+
else if (t.level >= 2) tank_val += 2300.0;
|
|
764
|
+
|
|
765
|
+
if (can_fire(s, i)) {
|
|
766
|
+
tank_val += 95.0;
|
|
767
|
+
} else {
|
|
768
|
+
for (int k = 0; k < s.n_bullets; ++k) {
|
|
769
|
+
if (s.bullet[k].owner == i) {
|
|
770
|
+
int bp = s.bullet[k].pos;
|
|
771
|
+
int bd = s.bullet[k].dir;
|
|
772
|
+
int steps_left = 0;
|
|
773
|
+
for (int st = 1; st <= 12; ++st) {
|
|
774
|
+
int nc = bp + st * DIR_DELTA[bd];
|
|
775
|
+
uint8_t g = s.grid[nc];
|
|
776
|
+
if (g != EMPTY && g != WATER) break;
|
|
777
|
+
if (tank_at(s, nc) >= 0) break;
|
|
778
|
+
steps_left++;
|
|
779
|
+
}
|
|
780
|
+
if (steps_left >= 3 && bullet_cap(s, i) == 1) {
|
|
781
|
+
tank_val -= steps_left * 30.0;
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
int survival_options = 0;
|
|
788
|
+
Cmd list[N_CMDS];
|
|
789
|
+
int nc = legal_commands(s, i, list);
|
|
790
|
+
for (int k = 0; k < nc; ++k) {
|
|
791
|
+
MoveSafety ms = check_cmd_safety(s, i, list[k]);
|
|
792
|
+
if (ms.existing_bullet_hits == 0 && !is_enemy_spawn_soon(s, seat, ms.p2)) {
|
|
793
|
+
survival_options++;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
if (survival_options == 0) {
|
|
797
|
+
if (t.hp <= 1) tank_val -= (700.0 + t.hp * 480.0 + t.level * 1100.0 + 600.0);
|
|
798
|
+
else tank_val -= 550.0;
|
|
799
|
+
} else if (survival_options <= 2) {
|
|
800
|
+
tank_val -= 110.0;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
val += sgn * tank_val;
|
|
804
|
+
|
|
805
|
+
int d_atk = G.dist_atk[i][t.pos];
|
|
806
|
+
int op_seat = 1 - seat;
|
|
807
|
+
if (G.base_ray_dist[op_seat][t.pos] > 0) {
|
|
808
|
+
int rdist = G.base_ray_dist[op_seat][t.pos];
|
|
809
|
+
int rsteels = G.base_ray_steels[op_seat][t.pos];
|
|
810
|
+
bool can_st = (t.level >= 2 && s.P.level2_steel);
|
|
811
|
+
if (rsteels == 0 || can_st) {
|
|
812
|
+
int cur_walls = 0;
|
|
813
|
+
int rdir = G.base_ray_dir[op_seat][t.pos];
|
|
814
|
+
for (int st = 1; st < rdist; ++st) {
|
|
815
|
+
uint8_t gg = s.grid[t.pos + st * DIR_DELTA[rdir]];
|
|
816
|
+
if (gg == BRICK || gg == STEEL) cur_walls++;
|
|
817
|
+
}
|
|
818
|
+
int dynamic_d = cur_walls * 4 + (rdist <= 2 ? 0 : (rdist - 2) * 2);
|
|
819
|
+
if (dynamic_d < d_atk) d_atk = dynamic_d;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
if (d_atk < best_atk_dist[seat]) {
|
|
823
|
+
second_atk_dist[seat] = best_atk_dist[seat];
|
|
824
|
+
best_atk_dist[seat] = d_atk;
|
|
825
|
+
} else if (d_atk < second_atk_dist[seat]) {
|
|
826
|
+
second_atk_dist[seat] = d_atk;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
if (t.level < s.P.max_level) {
|
|
830
|
+
for (int k = 0; k < s.n_supply; ++k) {
|
|
831
|
+
int ds = G.dist_supply[k][t.pos];
|
|
832
|
+
if (ds < best_supply_dist[seat][k]) {
|
|
833
|
+
best_supply_dist[seat][k] = ds;
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// 6. Base Attack Progress comparison
|
|
840
|
+
for (int seat = 0; seat < 2; ++seat) {
|
|
841
|
+
double sgn = (seat == me) ? 1.0 : -1.0;
|
|
842
|
+
if (best_atk_dist[seat] < INF) {
|
|
843
|
+
val += sgn * (1200.0 - best_atk_dist[seat] * 42.0);
|
|
844
|
+
}
|
|
845
|
+
if (second_atk_dist[seat] < INF) {
|
|
846
|
+
val += sgn * (600.0 - second_atk_dist[seat] * 22.0);
|
|
847
|
+
}
|
|
848
|
+
for (int tp = 0; tp < 3; ++tp) {
|
|
849
|
+
int i = tank_id(seat, tp);
|
|
850
|
+
if (s.tank[i].alive) {
|
|
851
|
+
int da = G.dist_atk[i][s.tank[i].pos];
|
|
852
|
+
if (da < INF) val += sgn * (-da * 12.0);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
// 7. Star Control evaluation
|
|
858
|
+
int turns_to_spawn = INF;
|
|
859
|
+
if (s.turn < s.P.supply_first) {
|
|
860
|
+
turns_to_spawn = s.P.supply_first - s.turn;
|
|
861
|
+
} else {
|
|
862
|
+
int rem = (s.turn - s.P.supply_first) % s.P.supply_period;
|
|
863
|
+
turns_to_spawn = (rem == 0) ? s.P.supply_period : (s.P.supply_period - rem);
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
for (int k = 0; k < s.n_supply; ++k) {
|
|
867
|
+
bool has_star = ((s.star_mask >> k) & 1) != 0;
|
|
868
|
+
if (has_star || turns_to_spawn <= 6) {
|
|
869
|
+
double weight = has_star ? 45.0 : (7 - turns_to_spawn) * 5.5;
|
|
870
|
+
if (best_supply_dist[me][k] < INF) {
|
|
871
|
+
val += std::max(0.0, (12.0 - best_supply_dist[me][k]) * weight);
|
|
872
|
+
}
|
|
873
|
+
if (best_supply_dist[op][k] < INF) {
|
|
874
|
+
val -= std::max(0.0, (12.0 - best_supply_dist[op][k]) * weight);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
// 8. Defensive Interception using exact path distance G.dist_from_tank
|
|
880
|
+
for (int seat = 0; seat < 2; ++seat) {
|
|
881
|
+
double sgn = (seat == me) ? 1.0 : -1.0;
|
|
882
|
+
int enemy = 1 - seat;
|
|
883
|
+
for (int et = 0; et < 3; ++et) {
|
|
884
|
+
int eid = tank_id(enemy, et);
|
|
885
|
+
if (!s.tank[eid].alive) continue;
|
|
886
|
+
int th = enemy_base_threat_level(s, seat, eid);
|
|
887
|
+
if (th >= 2) {
|
|
888
|
+
int min_def_dist = INF;
|
|
889
|
+
for (int mt = 0; mt < 3; ++mt) {
|
|
890
|
+
int mid = tank_id(seat, mt);
|
|
891
|
+
if (s.tank[mid].alive) {
|
|
892
|
+
int d = G.dist_from_tank[eid][s.tank[mid].pos];
|
|
893
|
+
if (d < min_def_dist) min_def_dist = d;
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
if (min_def_dist < INF) {
|
|
897
|
+
double urgency = (th == 3) ? 48.0 : 24.0;
|
|
898
|
+
val -= sgn * min_def_dist * urgency;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
for (int a = 0; a < 3; ++a) {
|
|
904
|
+
int ia = tank_id(seat, a);
|
|
905
|
+
if (!s.tank[ia].alive) continue;
|
|
906
|
+
for (int b = a + 1; b < 3; ++b) {
|
|
907
|
+
int ib = tank_id(seat, b);
|
|
908
|
+
if (!s.tank[ib].alive) continue;
|
|
909
|
+
int md = manhattan(s.tank[ia].pos, s.tank[ib].pos);
|
|
910
|
+
if (md == 1) val -= sgn * 25.0;
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
return val;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
struct ScoredCmd {
|
|
919
|
+
Cmd c;
|
|
920
|
+
double sc;
|
|
921
|
+
MoveSafety ms;
|
|
922
|
+
};
|
|
923
|
+
|
|
924
|
+
struct ScoredOrders {
|
|
925
|
+
Orders ord;
|
|
926
|
+
double prior;
|
|
927
|
+
};
|
|
928
|
+
|
|
929
|
+
int generate_candidate_orders(const State& s, int seat, ScoredOrders* out, int max_out) {
|
|
930
|
+
ScoredCmd top_cmds[3][8];
|
|
931
|
+
int n_top[3] = {0, 0, 0};
|
|
932
|
+
|
|
933
|
+
for (int type = 0; type < 3; ++type) {
|
|
934
|
+
int i = tank_id(seat, type);
|
|
935
|
+
if (!s.tank[i].alive) {
|
|
936
|
+
top_cmds[type][0] = ScoredCmd{STAY, 0.0, MoveSafety{}};
|
|
937
|
+
n_top[type] = 1;
|
|
938
|
+
continue;
|
|
939
|
+
}
|
|
940
|
+
Cmd list[N_CMDS];
|
|
941
|
+
int n = legal_commands(s, i, list);
|
|
942
|
+
ScoredCmd all[N_CMDS];
|
|
943
|
+
for (int k = 0; k < n; ++k) {
|
|
944
|
+
all[k].c = list[k];
|
|
945
|
+
all[k].sc = score_single_cmd(s, i, list[k], true);
|
|
946
|
+
all[k].ms = check_cmd_safety(s, i, list[k]);
|
|
947
|
+
}
|
|
948
|
+
std::sort(all, all + n, [](const ScoredCmd& a, const ScoredCmd& b) {
|
|
949
|
+
return a.sc > b.sc;
|
|
950
|
+
});
|
|
951
|
+
|
|
952
|
+
int keep = std::min(n, (type == FAST ? 5 : 4));
|
|
953
|
+
for (int k = 0; k < keep; ++k) {
|
|
954
|
+
top_cmds[type][k] = all[k];
|
|
955
|
+
}
|
|
956
|
+
n_top[type] = keep;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
ScoredOrders pool[120];
|
|
960
|
+
int n_pool = 0;
|
|
961
|
+
int op = 1 - seat;
|
|
962
|
+
|
|
963
|
+
for (int a = 0; a < n_top[0]; ++a) {
|
|
964
|
+
for (int b = 0; b < n_top[1]; ++b) {
|
|
965
|
+
for (int c = 0; c < n_top[2]; ++c) {
|
|
966
|
+
const ScoredCmd* scs[3] = {&top_cmds[0][a], &top_cmds[1][b], &top_cmds[2][c]};
|
|
967
|
+
double total = scs[0]->sc + scs[1]->sc + scs[2]->sc;
|
|
968
|
+
|
|
969
|
+
for (int u = 0; u < 3; ++u) {
|
|
970
|
+
int iu = tank_id(seat, u);
|
|
971
|
+
if (!s.tank[iu].alive) continue;
|
|
972
|
+
for (int v = u + 1; v < 3; ++v) {
|
|
973
|
+
int iv = tank_id(seat, v);
|
|
974
|
+
if (!s.tank[iv].alive) continue;
|
|
975
|
+
if (scs[u]->ms.p1 == scs[v]->ms.p1 || scs[u]->ms.p2 == scs[v]->ms.p2) {
|
|
976
|
+
total -= 600.0;
|
|
977
|
+
}
|
|
978
|
+
if (scs[u]->ms.p1 == s.tank[iv].pos && scs[v]->ms.p1 == s.tank[iu].pos) {
|
|
979
|
+
total -= 600.0;
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
int base_shots = 0;
|
|
985
|
+
for (int u = 0; u < 3; ++u) {
|
|
986
|
+
int iu = tank_id(seat, u);
|
|
987
|
+
if (!s.tank[iu].alive || !is_fire(scs[u]->c)) continue;
|
|
988
|
+
int d = scs[u]->c - 5;
|
|
989
|
+
int pos = s.tank[iu].pos;
|
|
990
|
+
if (G.base_ray_dist[op][pos] > 0 && G.base_ray_dir[op][pos] == d &&
|
|
991
|
+
G.base_ray_bricks[op][pos] == 0 && G.base_ray_steels[op][pos] == 0 &&
|
|
992
|
+
G.base_ray_dist[op][pos] <= 2) {
|
|
993
|
+
base_shots++;
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
if (base_shots >= 2) total -= 750.0 * (base_shots - 1);
|
|
997
|
+
|
|
998
|
+
Orders ord;
|
|
999
|
+
ord.cmd[0] = scs[0]->c;
|
|
1000
|
+
ord.cmd[1] = scs[1]->c;
|
|
1001
|
+
ord.cmd[2] = scs[2]->c;
|
|
1002
|
+
pool[n_pool++] = ScoredOrders{ord, total};
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
std::sort(pool, pool + n_pool, [](const ScoredOrders& x, const ScoredOrders& y) {
|
|
1008
|
+
return x.prior > y.prior;
|
|
1009
|
+
});
|
|
1010
|
+
|
|
1011
|
+
int res = std::min(n_pool, max_out);
|
|
1012
|
+
for (int k = 0; k < res; ++k) out[k] = pool[k];
|
|
1013
|
+
return res;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
} // namespace
|
|
1017
|
+
|
|
1018
|
+
void init(const State& s, int me) {
|
|
1019
|
+
(void)me;
|
|
1020
|
+
std::memset(last_pos_hist, -1, sizeof(last_pos_hist));
|
|
1021
|
+
last_op_base_hp[0] = s.base_hp[1];
|
|
1022
|
+
last_op_base_hp[1] = s.base_hp[0];
|
|
1023
|
+
no_dmg_turns[0] = no_dmg_turns[1] = 0;
|
|
1024
|
+
hist_turn = -1;
|
|
1025
|
+
precompute(s);
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
Orders decide(const State& s, int me) {
|
|
1029
|
+
int op = 1 - me;
|
|
1030
|
+
if (s.turn != hist_turn) {
|
|
1031
|
+
for (int i = 0; i < 6; ++i) {
|
|
1032
|
+
for (int h = 5; h > 0; --h) last_pos_hist[i][h] = last_pos_hist[i][h - 1];
|
|
1033
|
+
last_pos_hist[i][0] = s.tank[i].alive ? s.tank[i].pos : -1;
|
|
1034
|
+
}
|
|
1035
|
+
if (s.base_hp[op] < last_op_base_hp[me]) no_dmg_turns[me] = 0;
|
|
1036
|
+
else no_dmg_turns[me]++;
|
|
1037
|
+
last_op_base_hp[me] = s.base_hp[op];
|
|
1038
|
+
hist_turn = s.turn;
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
precompute(s);
|
|
1042
|
+
|
|
1043
|
+
constexpr int MAX_MY = 28;
|
|
1044
|
+
constexpr int MAX_OP = 20;
|
|
1045
|
+
ScoredOrders my_cands[MAX_MY];
|
|
1046
|
+
ScoredOrders op_cands[MAX_OP];
|
|
1047
|
+
|
|
1048
|
+
int n_my = generate_candidate_orders(s, me, my_cands, MAX_MY);
|
|
1049
|
+
int n_op = generate_candidate_orders(s, op, op_cands, MAX_OP);
|
|
1050
|
+
|
|
1051
|
+
if (n_my <= 0) return Orders{};
|
|
1052
|
+
if (n_my == 1) return my_cands[0].ord;
|
|
1053
|
+
|
|
1054
|
+
double op_weights[MAX_OP];
|
|
1055
|
+
double max_op_p = op_cands[0].prior;
|
|
1056
|
+
double sum_op_w = 0.0;
|
|
1057
|
+
for (int j = 0; j < n_op; ++j) {
|
|
1058
|
+
op_weights[j] = std::exp(std::max(-10.0, (op_cands[j].prior - max_op_p) / 400.0));
|
|
1059
|
+
sum_op_w += op_weights[j];
|
|
1060
|
+
}
|
|
1061
|
+
for (int j = 0; j < n_op; ++j) op_weights[j] /= sum_op_w;
|
|
1062
|
+
|
|
1063
|
+
static double mat[MAX_MY][MAX_OP];
|
|
1064
|
+
static State s1_cache[MAX_MY][MAX_OP];
|
|
1065
|
+
|
|
1066
|
+
for (int i = 0; i < n_my; ++i) {
|
|
1067
|
+
for (int j = 0; j < n_op; ++j) {
|
|
1068
|
+
State& s1 = s1_cache[i][j];
|
|
1069
|
+
s1 = s;
|
|
1070
|
+
step_as(s1, me, my_cands[i].ord, op_cands[j].ord);
|
|
1071
|
+
mat[i][j] = evaluate_state(s1, me) + my_cands[i].prior * 0.08;
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
for (int i = 0; i < n_my; ++i) {
|
|
1076
|
+
if (budget_left() < 6.0) break;
|
|
1077
|
+
for (int j = 0; j < std::min(n_op, 12); ++j) {
|
|
1078
|
+
if (budget_left() < 5.0) break;
|
|
1079
|
+
const State& s1 = s1_cache[i][j];
|
|
1080
|
+
if (s1.done) continue;
|
|
1081
|
+
State s2 = s1;
|
|
1082
|
+
Orders r0 = fast_rollout_orders(s2, 0);
|
|
1083
|
+
Orders r1 = fast_rollout_orders(s2, 1);
|
|
1084
|
+
step(s2, r0, r1);
|
|
1085
|
+
double v2 = evaluate_state(s2, me);
|
|
1086
|
+
|
|
1087
|
+
if (!s2.done && j < 6 && budget_left() > 10.0) {
|
|
1088
|
+
State s3 = s2;
|
|
1089
|
+
Orders r30 = fast_rollout_orders(s3, 0);
|
|
1090
|
+
Orders r31 = fast_rollout_orders(s3, 1);
|
|
1091
|
+
step(s3, r30, r31);
|
|
1092
|
+
double v3 = evaluate_state(s3, me);
|
|
1093
|
+
mat[i][j] = 0.45 * mat[i][j] + 0.35 * v2 + 0.20 * v3;
|
|
1094
|
+
} else {
|
|
1095
|
+
mat[i][j] = 0.55 * mat[i][j] + 0.45 * v2;
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
int best_idx = 0;
|
|
1101
|
+
double best_total = -1e18;
|
|
1102
|
+
int top_op_k = std::min(n_op, 8);
|
|
1103
|
+
|
|
1104
|
+
for (int i = 0; i < n_my; ++i) {
|
|
1105
|
+
double worst_top = 1e18;
|
|
1106
|
+
double expected = 0.0;
|
|
1107
|
+
|
|
1108
|
+
for (int j = 0; j < n_op; ++j) {
|
|
1109
|
+
double v = mat[i][j];
|
|
1110
|
+
if (j < top_op_k && v < worst_top) worst_top = v;
|
|
1111
|
+
expected += op_weights[j] * v;
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
double softmin_num = 0.0;
|
|
1115
|
+
double softmin_den = 0.0;
|
|
1116
|
+
for (int j = 0; j < n_op; ++j) {
|
|
1117
|
+
double v = mat[i][j];
|
|
1118
|
+
double w = op_weights[j] * std::exp(std::min(12.0, (worst_top - v) / 600.0));
|
|
1119
|
+
softmin_num += w * v;
|
|
1120
|
+
softmin_den += w;
|
|
1121
|
+
}
|
|
1122
|
+
double softmin = (softmin_den > 0.0) ? (softmin_num / softmin_den) : worst_top;
|
|
1123
|
+
|
|
1124
|
+
double combined = 0.45 * expected + 0.35 * softmin + 0.20 * worst_top;
|
|
1125
|
+
if (combined > best_total) {
|
|
1126
|
+
best_total = combined;
|
|
1127
|
+
best_idx = i;
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
return my_cands[best_idx].ord;
|
|
1132
|
+
}
|