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,928 @@
|
|
|
1
|
+
// 由赛事脚本生成:sparring.cpp 加上配置里 sparring_defines 指定的宏。不要手改,改配置或源码。
|
|
2
|
+
#define SP_BUDGET 14000
|
|
3
|
+
#define SP_STARW 90
|
|
4
|
+
#define SP_LVV 70
|
|
5
|
+
#define SP_STAR_REACH 60
|
|
6
|
+
#line 1 "sparring.cpp"
|
|
7
|
+
// sparring.cpp —— 陪练(出题方的强力参照,不发给选手)。
|
|
8
|
+
//
|
|
9
|
+
// 分两层:
|
|
10
|
+
//
|
|
11
|
+
// 战略层(规则,每回合开头定一次,见 assign_roles):每辆车本回合的任务是进攻、拦截某辆敌车、还是在家门口待命。
|
|
12
|
+
// 默认留一辆专职守家(装甲型),对方有车逼近时最多派一辆去拦,其余进攻;长时间没有进展就全员进攻。
|
|
13
|
+
// 任务只决定评估函数里这辆车"离目标多远"按哪张距离图算。
|
|
14
|
+
//
|
|
15
|
+
// 战术层(搜索):六辆车各有一棵搜索树("分散式 UCT")。每次模拟从当前局面出发,六辆车各自在自己的树里
|
|
16
|
+
// 按 PUCT 选一条命令,合成双方的联合命令,用引擎精确前推一回合;这样往下走 SP_DEPTH 回合,
|
|
17
|
+
// 走出树的车改用一个便宜的规则策略接着走。沿途每回合用评估函数打分取平均,记回六棵树上走过的每个节点
|
|
18
|
+
// (对方的车记相反数)。三辆车的联合动作空间有上千种,但每棵树只管自己那辆车的十几条命令,
|
|
19
|
+
// 车与车的配合、对手的应对都通过"一起被前推"体现在分数里。对手和我方用同一套搜索,
|
|
20
|
+
// 所以得到的是"双方都在找好棋"的近似解,不依赖对某个固定对手模型的猜测。
|
|
21
|
+
//
|
|
22
|
+
// 评估函数(evaluate):基地血量、击毁数、每辆车离目标的回合数(被毁的车按出生点加复活时间算)、
|
|
23
|
+
// 血量和等级、星星、炮线上被打掉的砖、还在飞的子弹会打到什么。
|
|
24
|
+
//
|
|
25
|
+
// 剪枝(build_actions):开炮只留"打得到东西"的方向(敌车、基地、挡路的砖、正飞来的子弹、敌车可能走进来的线);
|
|
26
|
+
// 快速型的两步按终点去重,"出去再回来"只在有威胁时保留。每辆车剩十条左右,按规则先验排序。
|
|
27
|
+
//
|
|
28
|
+
// 思考预算有两种控制方式,用 SP_USE_TIME 切换:
|
|
29
|
+
// 0(默认,实验用):每回合固定前推 SP_BUDGET 次 step,结果完全确定,可复现。
|
|
30
|
+
// 1(正式比赛用):tank::budget_left() 小于 SP_TIME_MARGIN 就停;SP_BUDGET 仍作为上限兜底
|
|
31
|
+
// (budget_left 目前是占位实现,返回一个很大的数)。
|
|
32
|
+
//
|
|
33
|
+
// 可调的宏(都有默认值,build.py 的 bots.txt 里可以逐项覆盖):见下面"配置"一节。
|
|
34
|
+
#include "tank.hpp"
|
|
35
|
+
#include <cmath>
|
|
36
|
+
#include <cstdint>
|
|
37
|
+
#include <cstring>
|
|
38
|
+
#include <vector>
|
|
39
|
+
|
|
40
|
+
using namespace tank;
|
|
41
|
+
|
|
42
|
+
// ====================================================================
|
|
43
|
+
// 配置
|
|
44
|
+
// ====================================================================
|
|
45
|
+
#ifndef SP_BUDGET
|
|
46
|
+
#define SP_BUDGET 20000 // 每回合前推多少次 step
|
|
47
|
+
#endif
|
|
48
|
+
#ifndef SP_USE_TIME
|
|
49
|
+
#define SP_USE_TIME 0 // 1 = 按 budget_left() 停
|
|
50
|
+
#endif
|
|
51
|
+
#ifndef SP_TIME_MARGIN
|
|
52
|
+
#define SP_TIME_MARGIN 4 // budget_left() 小于这个数就停(毫秒;引擎建议留 2–3 毫秒,每 16 次模拟查一次)
|
|
53
|
+
#endif
|
|
54
|
+
#ifndef SP_DEPTH
|
|
55
|
+
#define SP_DEPTH 6 // 每次模拟前推几回合
|
|
56
|
+
#endif
|
|
57
|
+
#ifndef SP_UCB
|
|
58
|
+
#define SP_UCB 80 // 探索系数 × 100(PUCT:Q + c·P·sqrt(N)/(1+n))
|
|
59
|
+
#endif
|
|
60
|
+
#ifndef SP_PRIOR_T
|
|
61
|
+
#define SP_PRIOR_T 12 // 先验分换成概率时的温度:越小越相信规则给的先验
|
|
62
|
+
#endif
|
|
63
|
+
#ifndef SP_FPU
|
|
64
|
+
#define SP_FPU 0 // 没试过的命令的初始估值 = 节点均值 − 这个数 / 100
|
|
65
|
+
#endif
|
|
66
|
+
#ifndef SP_STARS
|
|
67
|
+
#define SP_STARS 1 // 0 = 评估里不看星星("不抢星"的对照版本)
|
|
68
|
+
#endif
|
|
69
|
+
#ifndef SP_STARW
|
|
70
|
+
#define SP_STARW 26 // 星星的吸引力:离星星 0 回合时的加分,每远一回合少 2 分("认真抢星"的版本用 90)
|
|
71
|
+
#endif
|
|
72
|
+
#ifndef SP_LVV
|
|
73
|
+
#define SP_LVV 20 // 评估里每一级值多少分("认真抢星"的版本用 70)
|
|
74
|
+
#endif
|
|
75
|
+
#ifndef SP_STAR_REACH
|
|
76
|
+
#define SP_STAR_REACH 24 // 走位先验里,离星星不超过这么多(1/4 回合)才考虑绕过去
|
|
77
|
+
#endif
|
|
78
|
+
#ifndef SP_FFFIX
|
|
79
|
+
#define SP_FFFIX 1 // 1 = 出手前检查:选定的命令会不会当回合打中队友,会就把开炮的那辆换成次优命令
|
|
80
|
+
#endif
|
|
81
|
+
#ifndef SP_TV
|
|
82
|
+
#define SP_TV 22 // 快速型、装甲型耽误一回合值多少分(基地 1 血 = 100 分)
|
|
83
|
+
#endif
|
|
84
|
+
#ifndef SP_TV_FIRE
|
|
85
|
+
#define SP_TV_FIRE 34 // 火力型耽误一回合值多少分(它到位后每回合能打 1 血,是另外两种的两倍)
|
|
86
|
+
#endif
|
|
87
|
+
#ifndef SP_THREAT
|
|
88
|
+
#define SP_THREAT 40 // 敌车离"能打到我方基地"不到这么多(1/4 回合)就算威胁,要派车去拦
|
|
89
|
+
#endif
|
|
90
|
+
#ifndef SP_MAX_HUNTERS
|
|
91
|
+
#define SP_MAX_HUNTERS 1 // 最多几辆车去拦截(其余进攻)
|
|
92
|
+
#endif
|
|
93
|
+
#ifndef SP_EMERGENCY
|
|
94
|
+
#define SP_EMERGENCY 0 // 敌车离"能打到我方基地"不到这么多(1/4 回合)算紧急,紧急的有几辆就可以派几辆去拦;0 = 不启用
|
|
95
|
+
#endif
|
|
96
|
+
#ifndef SP_GUARDS
|
|
97
|
+
#define SP_GUARDS 1 // 专职守家的车数(先装甲型,再快速型):对方有车过了半场就留在家门口等,没有才去进攻;9 = 按来犯数量自动定
|
|
98
|
+
#endif
|
|
99
|
+
#ifndef SP_GUARD_RANGE
|
|
100
|
+
#define SP_GUARD_RANGE 48 // 对方有车离"能打到我方基地"不到这么多(1/4 回合),守家的车就不出门
|
|
101
|
+
#endif
|
|
102
|
+
#ifndef SP_FAR_COST
|
|
103
|
+
#define SP_FAR_COST 3 // 射击位离基地每远一格(超过 2 格的部分)的代价(1/4 回合):远了子弹飞得久、容易被挡被抵消
|
|
104
|
+
#endif
|
|
105
|
+
#ifndef SP_BLOCK_STILL
|
|
106
|
+
#define SP_BLOCK_STILL 0 // 炮线上的车连续这么多回合没动才算堵住炮线;0 = 只要在炮线上就算
|
|
107
|
+
#endif
|
|
108
|
+
#ifndef SP_HUNT_MAX
|
|
109
|
+
#define SP_HUNT_MAX 64 // 拦截的车最远从多远赶过去(1/4 回合)
|
|
110
|
+
#endif
|
|
111
|
+
#ifndef SP_SQUASH
|
|
112
|
+
#define SP_SQUASH 600 // 评估分压到 (-1, 1) 时的尺度
|
|
113
|
+
#endif
|
|
114
|
+
#ifndef SP_PATH_AVG
|
|
115
|
+
#define SP_PATH_AVG 1 // 1 = 一次模拟的分数取沿途每回合评估的平均;0 = 只看最后一回合(对照用)
|
|
116
|
+
#endif
|
|
117
|
+
#ifndef SP_METHOD
|
|
118
|
+
#define SP_METHOD 0 // 0 = 分散式 UCT(默认);1 = 一回合前推 + 规则续推 + 轮流取最佳应对(对照用的另一种搜索)
|
|
119
|
+
#endif
|
|
120
|
+
#ifndef SP_IBR_ROUNDS
|
|
121
|
+
#define SP_IBR_ROUNDS 3 // SP_METHOD = 1 时轮流优化几轮
|
|
122
|
+
#endif
|
|
123
|
+
#ifndef SP_ROLLOUT_RANDOM
|
|
124
|
+
#define SP_ROLLOUT_RANDOM 4 // 树外策略每 N 次有一次乱走
|
|
125
|
+
#endif
|
|
126
|
+
|
|
127
|
+
static const int BIG = 30000;
|
|
128
|
+
static const int MAX_ACTS = 22;
|
|
129
|
+
|
|
130
|
+
// ====================================================================
|
|
131
|
+
// 每回合开头算一次的地图信息(墙没变就沿用)
|
|
132
|
+
// ====================================================================
|
|
133
|
+
struct Maps {
|
|
134
|
+
bool valid = false;
|
|
135
|
+
uint64_t wall_hash = 0;
|
|
136
|
+
int16_t atk[2][CELLS]; // atk[seat][格]:seat 的慢车从这格出发,到"能有效射击对方基地"的代价,单位 1/4 回合
|
|
137
|
+
int16_t sup[MAX_SUPPLY][CELLS]; // 到各补给点的代价,同单位
|
|
138
|
+
int n_key[2]; // key[seat]:seat 进攻炮线上的砖(打掉它对 seat 有利)
|
|
139
|
+
int16_t key_cell[2][24];
|
|
140
|
+
int16_t key_val[2][24];
|
|
141
|
+
};
|
|
142
|
+
static Maps M;
|
|
143
|
+
|
|
144
|
+
static void flood(const State& s, int16_t* dist) {
|
|
145
|
+
static int queue[CELLS * 16];
|
|
146
|
+
int head = 0, tail = 0;
|
|
147
|
+
for (int c = 0; c < CELLS; ++c)
|
|
148
|
+
if (dist[c] < BIG) queue[tail++] = c;
|
|
149
|
+
while (head < tail) {
|
|
150
|
+
const int c = queue[head++ % (CELLS * 16)];
|
|
151
|
+
for (int d = 0; d < 4; ++d) {
|
|
152
|
+
const int n = c + DIR_DELTA[d];
|
|
153
|
+
const uint8_t g = s.grid[n];
|
|
154
|
+
if (g != EMPTY && g != BRICK) continue;
|
|
155
|
+
const int nd = dist[c] + (g == BRICK ? 12 : 4); // 空地一步 1 回合;砖墙要先打掉,算 3 回合
|
|
156
|
+
if (nd < dist[n]) { dist[n] = int16_t(nd); queue[tail++ % (CELLS * 16)] = n; }
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
static void build_maps(const State& s) {
|
|
162
|
+
if (M.valid && M.wall_hash == s.wall_hash) return;
|
|
163
|
+
M.valid = true;
|
|
164
|
+
M.wall_hash = s.wall_hash;
|
|
165
|
+
for (int seat = 0; seat < 2; ++seat) {
|
|
166
|
+
int16_t* dist = M.atk[seat];
|
|
167
|
+
for (int c = 0; c < CELLS; ++c) dist[c] = BIG;
|
|
168
|
+
M.n_key[seat] = 0;
|
|
169
|
+
// 基地只能沿它所在的行、列被打到。从对方基地朝四个方向走出去,沿途的空地就是射击位。
|
|
170
|
+
for (int d = 0; d < 4; ++d) {
|
|
171
|
+
int bricks = 0, k = 1;
|
|
172
|
+
for (int c = s.base_pos[1 - seat] + DIR_DELTA[d];; c += DIR_DELTA[d], ++k) {
|
|
173
|
+
const uint8_t g = s.grid[c];
|
|
174
|
+
if (g == BRICK) {
|
|
175
|
+
if (M.n_key[seat] < 24) {
|
|
176
|
+
M.key_cell[seat][M.n_key[seat]] = int16_t(c);
|
|
177
|
+
M.key_val[seat][M.n_key[seat]++] = int16_t(bricks == 0 ? 16 : 8);
|
|
178
|
+
}
|
|
179
|
+
dist[c] = int16_t(8 * bricks + 12 + (k > 2 ? 2 * (k - 2) : 0)); // 打掉这块砖站进去也是射击位
|
|
180
|
+
++bricks;
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (g == WATER) continue;
|
|
184
|
+
if (g != EMPTY) break;
|
|
185
|
+
dist[c] = int16_t(8 * bricks + (k > 2 ? 2 * (k - 2) : 0)); // 每块砖多打一炮;超过 2 格子弹当回合到不了
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
flood(s, dist);
|
|
189
|
+
}
|
|
190
|
+
for (int k = 0; k < s.n_supply; ++k) {
|
|
191
|
+
for (int c = 0; c < CELLS; ++c) M.sup[k][c] = BIG;
|
|
192
|
+
M.sup[k][s.supply[k]] = 0;
|
|
193
|
+
flood(s, M.sup[k]);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ====================================================================
|
|
198
|
+
// 评估函数:从 0 号方的角度给局面打分(基地 1 血 = 100 分)
|
|
199
|
+
// ====================================================================
|
|
200
|
+
static uint8_t root_grid[CELLS]; // 本回合开头的地形(用来看模拟里哪些关键的砖被打掉了)
|
|
201
|
+
static int soon_mask; // 马上要出星的补给点(提前去占位)
|
|
202
|
+
|
|
203
|
+
// 每辆车自己的进攻图:和 M.atk 一样,但把别的车此刻占着的格子算贵一些(多 2 回合),
|
|
204
|
+
// 这样通道被堵住(比如两辆车在窄道里对射僵住)时会绕路,而不是排在后面干等。每回合重算。
|
|
205
|
+
static int16_t atk_of[6][CELLS];
|
|
206
|
+
|
|
207
|
+
static void flood(const State& s, int16_t* dist);
|
|
208
|
+
|
|
209
|
+
static int still[6], still_pos[6]; // 每辆车已经连续几回合待在同一格
|
|
210
|
+
|
|
211
|
+
static void build_tank_maps(const State& s) {
|
|
212
|
+
for (int j = 0; j < 6; ++j) {
|
|
213
|
+
if (s.tank[j].alive && s.tank[j].pos == still_pos[j]) still[j] += 1;
|
|
214
|
+
else still[j] = 0;
|
|
215
|
+
still_pos[j] = s.tank[j].alive ? s.tank[j].pos : -1;
|
|
216
|
+
}
|
|
217
|
+
for (int i = 0; i < 6; ++i) {
|
|
218
|
+
const int seat = i / 3;
|
|
219
|
+
int16_t* dist = atk_of[i];
|
|
220
|
+
for (int c = 0; c < CELLS; ++c) dist[c] = BIG;
|
|
221
|
+
for (int d = 0; d < 4; ++d) {
|
|
222
|
+
int bricks = 0, k = 1;
|
|
223
|
+
for (int c = s.base_pos[1 - seat] + DIR_DELTA[d];; c += DIR_DELTA[d], ++k) {
|
|
224
|
+
const uint8_t g = s.grid[c];
|
|
225
|
+
if (g == BRICK) {
|
|
226
|
+
// 炮线上的砖:打掉之后站进去也是射击位(两侧被钢墙封住时,围墙那一格是唯一的侧面射击位)。
|
|
227
|
+
dist[c] = int16_t(8 * bricks + 12 + (k > 2 ? SP_FAR_COST * (k - 2) : 0));
|
|
228
|
+
++bricks;
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
if (g == WATER) continue;
|
|
232
|
+
if (g != EMPTY) break;
|
|
233
|
+
dist[c] = int16_t(8 * bricks + (k > 2 ? SP_FAR_COST * (k - 2) : 0));
|
|
234
|
+
// 炮线上有别的车(不是自己):它后面的格子打不到基地,不算射击位。
|
|
235
|
+
// (不这样的话,车会站在老远的地方隔着对方的车"打基地",两边子弹互相抵消,谁也不动。)
|
|
236
|
+
bool blocked = false;
|
|
237
|
+
for (int j = 0; j < 6; ++j) blocked |= j != i && s.tank[j].alive && s.tank[j].pos == c && still[j] >= SP_BLOCK_STILL;
|
|
238
|
+
if (blocked) break;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
// 带"别的车占着的格子更贵"的最短路。
|
|
242
|
+
static int queue[CELLS * 16];
|
|
243
|
+
static int16_t extra[CELLS];
|
|
244
|
+
for (int c = 0; c < CELLS; ++c) extra[c] = 0;
|
|
245
|
+
for (int j = 0; j < 6; ++j)
|
|
246
|
+
if (j != i && s.tank[j].alive) extra[s.tank[j].pos] = int16_t(still[j] >= 3 ? 24 : 8); // 站着不动好几回合的:当它是堵死的
|
|
247
|
+
int head = 0, tail = 0;
|
|
248
|
+
for (int c = 0; c < CELLS; ++c)
|
|
249
|
+
if (dist[c] < BIG) queue[tail++] = c;
|
|
250
|
+
while (head < tail) {
|
|
251
|
+
const int c = queue[head++ % (CELLS * 16)];
|
|
252
|
+
for (int d = 0; d < 4; ++d) {
|
|
253
|
+
const int n = c + DIR_DELTA[d];
|
|
254
|
+
const uint8_t g = s.grid[n];
|
|
255
|
+
if (g != EMPTY && g != BRICK) continue;
|
|
256
|
+
const int nd = dist[c] + (g == BRICK ? 12 : 4) + extra[c]; // 从 n 走进 c:c 上有别的车就多算
|
|
257
|
+
if (nd < dist[n]) { dist[n] = int16_t(nd); queue[tail++ % (CELLS * 16)] = n; }
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// 一辆车离"能有效射击对方基地"还差多少,单位 1/4 回合。快速型按 0.6 倍算。
|
|
264
|
+
static inline int progress_cost(int i, int pos) {
|
|
265
|
+
int a = atk_of[i][pos];
|
|
266
|
+
if (a > 240) a = 240;
|
|
267
|
+
return i % 3 == FAST ? a * 5 / 8 : a;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// ---- 分工(每回合开头按规则定,搜索期间不变)----
|
|
271
|
+
// 我方每辆车要么进攻(目标是对方基地的射击位),要么拦截某一辆逼近我方基地的敌车。
|
|
272
|
+
// 搜索负责"怎么走、什么时候开炮",分工只决定评估函数里这辆车"离目标多远"按哪张图算。
|
|
273
|
+
static int root_me;
|
|
274
|
+
static int hunt_target[6]; // -1 = 进攻;否则是要拦截的敌车编号
|
|
275
|
+
static int16_t hunt_map[3][CELLS]; // hunt_map[k]:到"和第 k 辆敌车对上炮线"的代价
|
|
276
|
+
static int16_t post_map[CELLS]; // 到家门口的代价(守家的车没有目标时用)
|
|
277
|
+
static bool holding[6]; // 这辆车本回合在守家待命
|
|
278
|
+
static int prev_target[3];
|
|
279
|
+
|
|
280
|
+
static int stale_turns; // 已经连续多少回合"什么都没发生"(基地血量、击毁数、墙都没变)
|
|
281
|
+
static int stale_hp[2], stale_kills[2];
|
|
282
|
+
static uint64_t stale_walls;
|
|
283
|
+
|
|
284
|
+
static void assign_roles(const State& s, int me) {
|
|
285
|
+
// 僵持检测:局面长时间没有进展,就减少拦截的车、多派车去进攻,把僵局打破。
|
|
286
|
+
if (s.base_hp[0] != stale_hp[0] || s.base_hp[1] != stale_hp[1] || s.kills[0] != stale_kills[0] ||
|
|
287
|
+
s.kills[1] != stale_kills[1] || s.wall_hash != stale_walls) {
|
|
288
|
+
stale_turns = 0;
|
|
289
|
+
stale_hp[0] = s.base_hp[0]; stale_hp[1] = s.base_hp[1];
|
|
290
|
+
stale_kills[0] = s.kills[0]; stale_kills[1] = s.kills[1];
|
|
291
|
+
stale_walls = s.wall_hash;
|
|
292
|
+
} else {
|
|
293
|
+
stale_turns += 1;
|
|
294
|
+
}
|
|
295
|
+
int max_hunters = SP_MAX_HUNTERS;
|
|
296
|
+
if (stale_turns >= 10 && max_hunters > 1) max_hunters = 1;
|
|
297
|
+
if (stale_turns >= 30) max_hunters = 0;
|
|
298
|
+
int n_hunters = 0;
|
|
299
|
+
#if SP_EMERGENCY
|
|
300
|
+
// 已经有敌车到位、正在打我方基地(或一两回合内就能打):有几辆就最多派几辆去拦,不受上面的名额限制。
|
|
301
|
+
{
|
|
302
|
+
int urgent = 0;
|
|
303
|
+
for (int k = 0; k < 3; ++k) {
|
|
304
|
+
const int foe = 3 * (1 - me) + k;
|
|
305
|
+
if (!s.tank[foe].alive) continue;
|
|
306
|
+
int c = M.atk[1 - me][s.tank[foe].pos];
|
|
307
|
+
if (k == FAST) c = c * 5 / 8;
|
|
308
|
+
urgent += c <= SP_EMERGENCY;
|
|
309
|
+
}
|
|
310
|
+
if (urgent > max_hunters && stale_turns < 10) max_hunters = urgent;
|
|
311
|
+
}
|
|
312
|
+
#endif
|
|
313
|
+
root_me = me;
|
|
314
|
+
for (int i = 0; i < 6; ++i) { hunt_target[i] = -1; holding[i] = false; }
|
|
315
|
+
const int foe0 = 3 * (1 - me);
|
|
316
|
+
int threat[3], threat_cost[3], n = 0;
|
|
317
|
+
for (int k = 0; k < 3; ++k) {
|
|
318
|
+
if (!s.tank[foe0 + k].alive) continue;
|
|
319
|
+
int c = M.atk[1 - me][s.tank[foe0 + k].pos];
|
|
320
|
+
if (k == FAST) c = c * 5 / 8;
|
|
321
|
+
if (c > SP_THREAT) continue;
|
|
322
|
+
int at = n++;
|
|
323
|
+
while (at > 0 && threat_cost[at - 1] > c) { threat[at] = threat[at - 1]; threat_cost[at] = threat_cost[at - 1]; --at; }
|
|
324
|
+
threat[at] = k;
|
|
325
|
+
threat_cost[at] = c;
|
|
326
|
+
}
|
|
327
|
+
bool used[3] = {};
|
|
328
|
+
int now_target[3] = {-1, -1, -1};
|
|
329
|
+
for (int q = 0; q < n && n_hunters < max_hunters; ++q) {
|
|
330
|
+
const int k = threat[q], foe = foe0 + k;
|
|
331
|
+
int16_t* dist = hunt_map[k];
|
|
332
|
+
for (int c = 0; c < CELLS; ++c) dist[c] = BIG;
|
|
333
|
+
for (int d = 0; d < 4; ++d) {
|
|
334
|
+
int steps = 1;
|
|
335
|
+
for (int c = s.tank[foe].pos + DIR_DELTA[d]; s.grid[c] == EMPTY || s.grid[c] == WATER; c += DIR_DELTA[d], ++steps)
|
|
336
|
+
if (s.grid[c] == EMPTY) dist[c] = int16_t(steps <= 2 ? 0 : 2 * (steps - 2));
|
|
337
|
+
}
|
|
338
|
+
flood(s, dist);
|
|
339
|
+
int best = -1, best_eta = SP_HUNT_MAX;
|
|
340
|
+
for (int t = 0; t < 3; ++t) {
|
|
341
|
+
if (used[t]) continue;
|
|
342
|
+
const int i = 3 * me + t;
|
|
343
|
+
const bool alive = s.tank[i].alive;
|
|
344
|
+
const int pos = alive ? s.tank[i].pos : s.spawn[i];
|
|
345
|
+
int eta = dist[pos] >= BIG ? BIG : (t == FAST ? dist[pos] * 5 / 8 : dist[pos]);
|
|
346
|
+
if (!alive) eta += 4 * respawn_in(s, i);
|
|
347
|
+
if (alive && progress_cost(i, pos) <= 4 && eta > 8) continue; // 已经在打对方基地、离得又远:不回头
|
|
348
|
+
if (prev_target[t] == foe) eta -= 8; // 上回合就在追它:优先接着追
|
|
349
|
+
#if SP_GUARDS
|
|
350
|
+
if (!(t == ARMOR || (SP_GUARDS >= 2 && t == FAST) || SP_GUARDS == 3)) eta += 12; // 有专职守家的车时,优先派它们
|
|
351
|
+
#endif
|
|
352
|
+
if (eta < best_eta) { best_eta = eta; best = t; }
|
|
353
|
+
}
|
|
354
|
+
if (best >= 0) {
|
|
355
|
+
++n_hunters;
|
|
356
|
+
used[best] = true;
|
|
357
|
+
hunt_target[3 * me + best] = foe;
|
|
358
|
+
now_target[best] = foe;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
for (int t = 0; t < 3; ++t) prev_target[t] = now_target[t];
|
|
362
|
+
#if SP_GUARDS
|
|
363
|
+
// 专职守家的车:没被派去拦截、而对方有车已经过来了,就在家门口待命。
|
|
364
|
+
{
|
|
365
|
+
bool coming = false;
|
|
366
|
+
for (int k = 0; k < 3; ++k) {
|
|
367
|
+
if (!s.tank[foe0 + k].alive) continue;
|
|
368
|
+
int c = M.atk[1 - me][s.tank[foe0 + k].pos];
|
|
369
|
+
if (k == FAST) c = c * 5 / 8;
|
|
370
|
+
coming |= c <= SP_GUARD_RANGE;
|
|
371
|
+
}
|
|
372
|
+
static const int order[3] = {ARMOR, FAST, FIRE};
|
|
373
|
+
int n_guards = SP_GUARDS;
|
|
374
|
+
if (SP_GUARDS == 9) { // 9 = 自动:对方来几辆,就留"来的数量减一"辆(最多两辆)
|
|
375
|
+
int n_coming = 0;
|
|
376
|
+
for (int k = 0; k < 3; ++k) {
|
|
377
|
+
int c = M.atk[1 - me][s.tank[foe0 + k].alive ? s.tank[foe0 + k].pos : s.spawn[foe0 + k]];
|
|
378
|
+
if (k == FAST) c = c * 5 / 8;
|
|
379
|
+
n_coming += s.tank[foe0 + k].alive && c <= SP_GUARD_RANGE;
|
|
380
|
+
}
|
|
381
|
+
n_guards = n_coming - 1 - n_hunters;
|
|
382
|
+
if (n_guards > 2) n_guards = 2;
|
|
383
|
+
}
|
|
384
|
+
for (int q = 0; q < n_guards && q < 3; ++q) {
|
|
385
|
+
const int i = 3 * me + order[q];
|
|
386
|
+
if (coming && hunt_target[i] < 0 && stale_turns < 30) holding[i] = true;
|
|
387
|
+
}
|
|
388
|
+
for (int c = 0; c < CELLS; ++c) post_map[c] = BIG;
|
|
389
|
+
const int fwd = me == 0 ? UP : DOWN;
|
|
390
|
+
const int post = s.base_pos[me] + 3 * DIR_DELTA[fwd];
|
|
391
|
+
for (int d = 0; d < 4; ++d) {
|
|
392
|
+
const int c = post + DIR_DELTA[d];
|
|
393
|
+
if (s.grid[c] == EMPTY) post_map[c] = 0;
|
|
394
|
+
}
|
|
395
|
+
if (s.grid[post] == EMPTY) post_map[post] = 0;
|
|
396
|
+
flood(s, post_map);
|
|
397
|
+
}
|
|
398
|
+
#endif
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// 评估和走位用的"离目标多远":拦截的车看离目标敌车多远(它被打掉了就算到位),其余看进攻图。
|
|
402
|
+
static inline int tank_cost(const State& s, int i, int pos) {
|
|
403
|
+
const int target = hunt_target[i];
|
|
404
|
+
if (holding[i]) { const int a = post_map[pos]; return a > 240 ? 240 : (i % 3 == FAST ? a * 5 / 8 : a); }
|
|
405
|
+
if (target < 0) return progress_cost(i, pos);
|
|
406
|
+
if (!s.tank[target].alive) return 0;
|
|
407
|
+
int a = hunt_map[target % 3][pos];
|
|
408
|
+
if (a > 240) a = 240;
|
|
409
|
+
return i % 3 == FAST ? a * 5 / 8 : a;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
static int evaluate(const State& s) {
|
|
413
|
+
if (s.done) {
|
|
414
|
+
if (s.winner < 0) return 0;
|
|
415
|
+
return (s.winner == 0 ? 6000 : -6000) + 10 * (s.base_hp[0] - s.base_hp[1]);
|
|
416
|
+
}
|
|
417
|
+
static const int tv[3] = {SP_TV, SP_TV_FIRE, SP_TV};
|
|
418
|
+
int e[2] = {0, 0};
|
|
419
|
+
for (int seat = 0; seat < 2; ++seat) {
|
|
420
|
+
e[seat] += 100 * s.base_hp[seat] + 8 * s.kills[seat] + 2 * s.stars_eaten[seat];
|
|
421
|
+
for (int k = 0; k < M.n_key[seat]; ++k)
|
|
422
|
+
if (s.grid[M.key_cell[seat][k]] != BRICK && root_grid[M.key_cell[seat][k]] == BRICK) e[seat] += M.key_val[seat][k];
|
|
423
|
+
}
|
|
424
|
+
for (int i = 0; i < 6; ++i) {
|
|
425
|
+
const Tank& t = s.tank[i];
|
|
426
|
+
const int seat = i / 3, type = i % 3;
|
|
427
|
+
if (t.alive) {
|
|
428
|
+
e[seat] -= tv[type] * tank_cost(s, i, t.pos) / 4;
|
|
429
|
+
e[seat] += 15 * (t.hp - 1) + SP_LVV * t.level;
|
|
430
|
+
} else {
|
|
431
|
+
e[seat] -= tv[type] * (tank_cost(s, i, s.spawn[i]) + 4 * respawn_in(s, i)) / 4 + 10;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
#if SP_STARS
|
|
435
|
+
// 星星:没满级的车离星星越近越好(吃到之后体现在等级和血量上)。
|
|
436
|
+
const int mask = s.star_mask | soon_mask;
|
|
437
|
+
if (mask) {
|
|
438
|
+
for (int k = 0; k < s.n_supply; ++k) {
|
|
439
|
+
if (!((mask >> k) & 1)) continue;
|
|
440
|
+
for (int seat = 0; seat < 2; ++seat) {
|
|
441
|
+
int best = BIG;
|
|
442
|
+
for (int i = 3 * seat; i < 3 * seat + 3; ++i) {
|
|
443
|
+
if (!s.tank[i].alive || s.tank[i].level >= s.P.max_level) continue;
|
|
444
|
+
int a = M.sup[k][s.tank[i].pos];
|
|
445
|
+
if (i % 3 == FAST) a = a * 5 / 8;
|
|
446
|
+
if (a < best) best = a;
|
|
447
|
+
}
|
|
448
|
+
const int bonus = SP_STARW - best / 2; // 每远一回合少 2 分
|
|
449
|
+
if (bonus > 0) e[seat] += bonus;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
#endif
|
|
454
|
+
// 还在飞的子弹:沿弹道看它会先碰到什么。
|
|
455
|
+
for (int b = 0; b < s.n_bullets; ++b) {
|
|
456
|
+
const Bullet& bl = s.bullet[b];
|
|
457
|
+
int c = bl.pos;
|
|
458
|
+
for (int k = 1; k <= 8; ++k) {
|
|
459
|
+
c += DIR_DELTA[bl.dir];
|
|
460
|
+
const uint8_t g = s.grid[c];
|
|
461
|
+
if (g == WATER) continue;
|
|
462
|
+
if (g == BASE0 || g == BASE1) { e[g - BASE0] -= 70; break; }
|
|
463
|
+
if (g != EMPTY) break;
|
|
464
|
+
int v = -1;
|
|
465
|
+
for (int i = 0; i < 6; ++i)
|
|
466
|
+
if (s.tank[i].alive && s.tank[i].pos == c) v = i;
|
|
467
|
+
if (v >= 0) {
|
|
468
|
+
e[v / 3] -= k <= 2 ? 20 : 5;
|
|
469
|
+
break;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
return e[0] - e[1];
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// ====================================================================
|
|
477
|
+
// 候选命令:给一辆车列出值得搜的命令,按粗略的好坏排好序(没访问过的按这个顺序先试)
|
|
478
|
+
// ====================================================================
|
|
479
|
+
struct Scan { // 朝一个方向看过去的结果
|
|
480
|
+
bool useful; // 这个方向开炮有没有意义
|
|
481
|
+
int prior; // 开炮的先验分
|
|
482
|
+
bool danger; // 这个方向有威胁(子弹正飞来,或 2 格内有能开炮的敌车)
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
static Scan scan_dir(const State& s, int i, int d) {
|
|
486
|
+
Scan r{false, 0, false};
|
|
487
|
+
const int seat = i / 3;
|
|
488
|
+
int bricks = 0, c = s.tank[i].pos;
|
|
489
|
+
for (int k = 1; k <= 14; ++k) {
|
|
490
|
+
c += DIR_DELTA[d];
|
|
491
|
+
const uint8_t g = s.grid[c];
|
|
492
|
+
if (g == WATER) continue;
|
|
493
|
+
if (g == BRICK) {
|
|
494
|
+
if (bricks == 0) {
|
|
495
|
+
bool key = false;
|
|
496
|
+
for (int q = 0; q < M.n_key[seat]; ++q) key |= M.key_cell[seat][q] == c;
|
|
497
|
+
if (key && k <= 6) { r.useful = true; r.prior = r.prior > 62 ? r.prior : 62; }
|
|
498
|
+
else if (k <= 2) { r.useful = true; r.prior = r.prior > 30 ? r.prior : 30; }
|
|
499
|
+
}
|
|
500
|
+
++bricks;
|
|
501
|
+
continue;
|
|
502
|
+
}
|
|
503
|
+
if (g == BASE0 + (1 - seat)) {
|
|
504
|
+
if (k <= 6) { r.useful = true; const int p = 96 - 10 * bricks; r.prior = r.prior > p ? r.prior : p; }
|
|
505
|
+
break;
|
|
506
|
+
}
|
|
507
|
+
if (g == STEEL) {
|
|
508
|
+
if (bricks == 0 && k <= 2 && s.tank[i].level >= 2 && s.P.level2_steel) { r.useful = true; r.prior = r.prior > 25 ? r.prior : 25; }
|
|
509
|
+
break;
|
|
510
|
+
}
|
|
511
|
+
if (g != EMPTY) break;
|
|
512
|
+
if (bricks) continue;
|
|
513
|
+
for (int b = 0; b < s.n_bullets; ++b)
|
|
514
|
+
if (s.bullet[b].pos == c && s.bullet[b].dir == (d ^ 1) && k <= 6) {
|
|
515
|
+
r.useful = true;
|
|
516
|
+
r.danger = true;
|
|
517
|
+
r.prior = r.prior > 85 ? r.prior : 85;
|
|
518
|
+
}
|
|
519
|
+
int v = -1;
|
|
520
|
+
for (int j = 0; j < 6; ++j)
|
|
521
|
+
if (s.tank[j].alive && s.tank[j].pos == c) v = j;
|
|
522
|
+
if (v >= 0) {
|
|
523
|
+
if (v / 3 == seat) break; // 队友挡着
|
|
524
|
+
r.useful = true;
|
|
525
|
+
const bool armed = can_fire(s, v);
|
|
526
|
+
if (k <= 2 && armed) r.danger = true;
|
|
527
|
+
const int p = k <= 2 ? (armed ? 88 : 99) : (k <= 4 ? 64 : 44);
|
|
528
|
+
r.prior = r.prior > p ? r.prior : p;
|
|
529
|
+
break;
|
|
530
|
+
}
|
|
531
|
+
// 敌车就在炮线旁边一格,可能走进来:预判射击。
|
|
532
|
+
if (k <= 5 && !r.useful) {
|
|
533
|
+
const int side = (d < 2) ? 1 : STRIDE;
|
|
534
|
+
for (int j = 3 * (1 - seat); j < 3 * (1 - seat) + 3; ++j)
|
|
535
|
+
if (s.tank[j].alive && (s.tank[j].pos == c + side || s.tank[j].pos == c - side)) { r.useful = true; r.prior = 36; }
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
return r;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
struct Node {
|
|
542
|
+
int32_t visits;
|
|
543
|
+
float total; // 经过这个节点的所有模拟的分数之和
|
|
544
|
+
int32_t child[MAX_ACTS];
|
|
545
|
+
int32_t n[MAX_ACTS];
|
|
546
|
+
float sum[MAX_ACTS];
|
|
547
|
+
float p[MAX_ACTS]; // 先验概率
|
|
548
|
+
Cmd act[MAX_ACTS];
|
|
549
|
+
uint8_t n_acts;
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
static int goal_cost(const State& s, int i, int pos) { // 走位的先验:离目标(或星星)多远
|
|
553
|
+
if (hunt_target[i] >= 0) return hunt_map[hunt_target[i] % 3][pos];
|
|
554
|
+
if (holding[i]) return post_map[pos];
|
|
555
|
+
int a = atk_of[i][pos];
|
|
556
|
+
#if SP_STARS
|
|
557
|
+
if (s.tank[i].level < s.P.max_level && (s.star_mask | soon_mask)) {
|
|
558
|
+
for (int k = 0; k < s.n_supply; ++k)
|
|
559
|
+
if (((s.star_mask | soon_mask) >> k) & 1) {
|
|
560
|
+
const int b = M.sup[k][pos] + 8;
|
|
561
|
+
if (b < a && M.sup[k][pos] <= SP_STAR_REACH) a = b;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
#else
|
|
565
|
+
(void)s;
|
|
566
|
+
#endif
|
|
567
|
+
return a;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
static void build_actions(Node& nd, const State& s, int i) {
|
|
571
|
+
Cmd list[N_CMDS];
|
|
572
|
+
const int n = legal_commands(s, i, list);
|
|
573
|
+
const Tank& t = s.tank[i];
|
|
574
|
+
Scan sc[4];
|
|
575
|
+
bool danger = false;
|
|
576
|
+
for (int d = 0; d < 4; ++d) { sc[d] = scan_dir(s, i, d); danger |= sc[d].danger; }
|
|
577
|
+
const int here = goal_cost(s, i, t.pos);
|
|
578
|
+
Cmd acts[N_CMDS];
|
|
579
|
+
int prior[N_CMDS], m = 0;
|
|
580
|
+
int ends[N_CMDS], n_ends = 0; // 两步命令按终点去重
|
|
581
|
+
for (int k = 0; k < n; ++k) {
|
|
582
|
+
const Cmd c = list[k];
|
|
583
|
+
int p;
|
|
584
|
+
if (c == STAY) {
|
|
585
|
+
p = 46;
|
|
586
|
+
} else if (is_fire(c)) {
|
|
587
|
+
if (!sc[c - 5].useful) continue;
|
|
588
|
+
p = sc[c - 5].prior;
|
|
589
|
+
} else if (is_move(c)) {
|
|
590
|
+
const int to = t.pos + DIR_DELTA[c - 1];
|
|
591
|
+
p = 50 + (here - goal_cost(s, i, to)) * 2;
|
|
592
|
+
if (danger && !sc[c - 1].danger && !sc[(c - 1) ^ 1].danger) p += 30; // 横向让开
|
|
593
|
+
} else {
|
|
594
|
+
const int d1 = (c - 9) >> 2, d2 = (c - 9) & 3;
|
|
595
|
+
const int mid = t.pos + DIR_DELTA[d1];
|
|
596
|
+
if (s.grid[mid] != EMPTY) continue; // "原地等一小步再走":用处不大,不搜
|
|
597
|
+
const int to = mid + DIR_DELTA[d2];
|
|
598
|
+
if (to == t.pos) { // 出去再回来:只在有威胁时才值得(晃过一发子弹)
|
|
599
|
+
if (!danger || sc[d1].danger || sc[d2].danger) continue;
|
|
600
|
+
p = 70;
|
|
601
|
+
} else {
|
|
602
|
+
if (s.grid[to] != EMPTY) continue; // 第二步多半走不成,等于一步
|
|
603
|
+
bool dup = false;
|
|
604
|
+
for (int q = 0; q < n_ends; ++q) dup |= ends[q] == to;
|
|
605
|
+
if (dup) continue; // 同一个终点的两条折线只留一条
|
|
606
|
+
ends[n_ends++] = to;
|
|
607
|
+
p = 52 + (here - goal_cost(s, i, to)) * 2;
|
|
608
|
+
if (danger) p += 20;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
acts[m] = c;
|
|
612
|
+
prior[m++] = p;
|
|
613
|
+
}
|
|
614
|
+
// 按先验分从高到低排,最多留 MAX_ACTS 条。
|
|
615
|
+
for (int a = 1; a < m; ++a)
|
|
616
|
+
for (int b = a; b > 0 && prior[b] > prior[b - 1]; --b) {
|
|
617
|
+
const int tp = prior[b]; prior[b] = prior[b - 1]; prior[b - 1] = tp;
|
|
618
|
+
const Cmd tc = acts[b]; acts[b] = acts[b - 1]; acts[b - 1] = tc;
|
|
619
|
+
}
|
|
620
|
+
if (m > MAX_ACTS) m = MAX_ACTS;
|
|
621
|
+
nd.n_acts = uint8_t(m);
|
|
622
|
+
float z = 0;
|
|
623
|
+
for (int a = 0; a < m; ++a) {
|
|
624
|
+
nd.act[a] = acts[a];
|
|
625
|
+
nd.child[a] = -1;
|
|
626
|
+
nd.n[a] = 0;
|
|
627
|
+
nd.sum[a] = 0;
|
|
628
|
+
nd.p[a] = std::exp(float(prior[a] - prior[0]) / float(SP_PRIOR_T));
|
|
629
|
+
z += nd.p[a];
|
|
630
|
+
}
|
|
631
|
+
for (int a = 0; a < m; ++a) nd.p[a] /= z;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// ====================================================================
|
|
635
|
+
// 树外策略:便宜的规则走法(朝进攻位走,看得到目标就开炮)
|
|
636
|
+
// ====================================================================
|
|
637
|
+
static uint64_t rng_state;
|
|
638
|
+
static inline uint32_t rng() {
|
|
639
|
+
rng_state ^= rng_state << 13;
|
|
640
|
+
rng_state ^= rng_state >> 7;
|
|
641
|
+
rng_state ^= rng_state << 17;
|
|
642
|
+
return uint32_t(rng_state >> 16);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
static Cmd rollout_cmd(const State& s, int i) {
|
|
646
|
+
const Tank& t = s.tank[i];
|
|
647
|
+
const int seat = i / 3;
|
|
648
|
+
const uint32_t r = rng();
|
|
649
|
+
const bool armed = can_fire(s, i);
|
|
650
|
+
if (armed && (r & 7) != 0) {
|
|
651
|
+
for (int d = 0; d < 4; ++d) {
|
|
652
|
+
int c = t.pos, bricks = 0;
|
|
653
|
+
for (int k = 1; k <= 4; ++k) {
|
|
654
|
+
c += DIR_DELTA[d];
|
|
655
|
+
const uint8_t g = s.grid[c];
|
|
656
|
+
if (g == WATER) continue;
|
|
657
|
+
if (g == BRICK) { if (++bricks > 1) break; continue; }
|
|
658
|
+
if (g == BASE0 + (1 - seat)) return cmd_fire(d);
|
|
659
|
+
if (g != EMPTY) break;
|
|
660
|
+
if (bricks) continue;
|
|
661
|
+
int v = -1;
|
|
662
|
+
for (int j = 0; j < 6; ++j)
|
|
663
|
+
if (s.tank[j].alive && s.tank[j].pos == c) v = j;
|
|
664
|
+
if (v >= 0) {
|
|
665
|
+
if (v / 3 != seat && k <= 2) return cmd_fire(d);
|
|
666
|
+
break;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
const int16_t* dist = hunt_target[i] >= 0 ? hunt_map[hunt_target[i] % 3] : (holding[i] ? post_map : atk_of[i]);
|
|
672
|
+
int pos = t.pos, dirs[2] = {-1, -1};
|
|
673
|
+
const int steps = (i % 3 == FAST) ? 2 : 1;
|
|
674
|
+
for (int k = 0; k < steps; ++k) {
|
|
675
|
+
int best = -1, best_cost = dist[pos];
|
|
676
|
+
if (((r >> (8 + 4 * k)) & 15) < 16 / SP_ROLLOUT_RANDOM) {
|
|
677
|
+
const int d = (r >> (16 + 2 * k)) & 3;
|
|
678
|
+
if (s.grid[pos + DIR_DELTA[d]] == EMPTY) best = d;
|
|
679
|
+
} else {
|
|
680
|
+
for (int d = 0; d < 4; ++d) {
|
|
681
|
+
const int to = pos + DIR_DELTA[d];
|
|
682
|
+
if (dist[to] < best_cost) { best_cost = dist[to]; best = d; }
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
if (best < 0) break;
|
|
686
|
+
if (s.grid[pos + DIR_DELTA[best]] == BRICK) {
|
|
687
|
+
if (k == 0 && armed) return cmd_fire(best);
|
|
688
|
+
break;
|
|
689
|
+
}
|
|
690
|
+
dirs[k] = best;
|
|
691
|
+
pos += DIR_DELTA[best];
|
|
692
|
+
}
|
|
693
|
+
if (dirs[0] < 0) return STAY;
|
|
694
|
+
if (dirs[1] < 0) return cmd_move(dirs[0]);
|
|
695
|
+
return cmd_move2(dirs[0], dirs[1]);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// ====================================================================
|
|
699
|
+
// 搜索
|
|
700
|
+
// ====================================================================
|
|
701
|
+
static std::vector<Node> pool;
|
|
702
|
+
static int pool_used;
|
|
703
|
+
static long long total_steps; // 统计用:本局累计前推次数
|
|
704
|
+
|
|
705
|
+
static inline int new_node() {
|
|
706
|
+
if (pool_used >= int(pool.size())) return -1;
|
|
707
|
+
Node& nd = pool[pool_used];
|
|
708
|
+
nd.visits = 0;
|
|
709
|
+
nd.total = 0;
|
|
710
|
+
nd.n_acts = 0;
|
|
711
|
+
return pool_used++;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
static inline int select_action(const Node& nd) {
|
|
715
|
+
// PUCT:估值 + 探索项。探索项按先验概率分配,所以先验高的命令先被试、试得多;
|
|
716
|
+
// 没试过的命令估值取"节点均值减一点",不会六辆车同时乱试。
|
|
717
|
+
if (nd.visits == 0) return 0; // 第一次来:走先验最高的
|
|
718
|
+
const float sq = std::sqrt(float(nd.visits)) * (SP_UCB / 100.0f);
|
|
719
|
+
const float fpu = nd.total / float(nd.visits) - SP_FPU / 100.0f;
|
|
720
|
+
int best = 0;
|
|
721
|
+
float best_u = -1e9f;
|
|
722
|
+
for (int a = 0; a < nd.n_acts; ++a) {
|
|
723
|
+
const float q = nd.n[a] ? nd.sum[a] / float(nd.n[a]) : fpu;
|
|
724
|
+
const float u = q + sq * nd.p[a] / float(1 + nd.n[a]);
|
|
725
|
+
if (u > best_u) { best_u = u; best = a; }
|
|
726
|
+
}
|
|
727
|
+
return best;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
void init(const State& s, int me) {
|
|
731
|
+
(void)me;
|
|
732
|
+
M.valid = false;
|
|
733
|
+
total_steps = 0;
|
|
734
|
+
for (int t = 0; t < 3; ++t) prev_target[t] = -1;
|
|
735
|
+
for (int j = 0; j < 6; ++j) { still[j] = 0; still_pos[j] = -1; }
|
|
736
|
+
stale_turns = 0;
|
|
737
|
+
stale_hp[0] = stale_hp[1] = stale_kills[0] = stale_kills[1] = -1;
|
|
738
|
+
stale_walls = 0;
|
|
739
|
+
const size_t want = size_t(SP_BUDGET) / SP_DEPTH * 6 + 64;
|
|
740
|
+
if (pool.size() != (want < 600000 ? want : 600000)) pool.assign(want < 600000 ? want : 600000, Node{});
|
|
741
|
+
build_maps(s);
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
#if SP_METHOD == 1
|
|
745
|
+
// 对照用的另一种搜索:每辆车把自己的每条候选命令前推一回合(其余五辆用当前选定的命令),
|
|
746
|
+
// 之后所有车按树外的规则策略续推到 SP_DEPTH 回合,沿途评估取平均;六辆车轮流这样取最佳应对,做 SP_IBR_ROUNDS 轮。
|
|
747
|
+
// 这是"规则型 + 一回合前推 + 续推"的常规写法,预算用不满(每轮约 6 辆 × 十几条命令 × SP_DEPTH 次前推)。
|
|
748
|
+
static Orders decide_ibr(const State& s, int me) {
|
|
749
|
+
static Node nd[6];
|
|
750
|
+
Cmd choice[6];
|
|
751
|
+
for (int i = 0; i < 6; ++i) {
|
|
752
|
+
choice[i] = STAY;
|
|
753
|
+
nd[i].n_acts = 0;
|
|
754
|
+
if (!s.tank[i].alive) continue;
|
|
755
|
+
build_actions(nd[i], s, i);
|
|
756
|
+
choice[i] = nd[i].act[0];
|
|
757
|
+
}
|
|
758
|
+
const uint64_t seed = rng_state;
|
|
759
|
+
for (int round = 0; round < SP_IBR_ROUNDS; ++round) {
|
|
760
|
+
bool changed = false;
|
|
761
|
+
for (int k = 0; k < 6; ++k) {
|
|
762
|
+
const int i = (round & 1) ? 5 - k : k;
|
|
763
|
+
if (nd[i].n_acts <= 1) continue;
|
|
764
|
+
int best = -1;
|
|
765
|
+
long long best_score = 0;
|
|
766
|
+
for (int a = 0; a < nd[i].n_acts; ++a) {
|
|
767
|
+
Orders o[2];
|
|
768
|
+
for (int j = 0; j < 6; ++j) o[j / 3].cmd[j % 3] = j == i ? nd[i].act[a] : choice[j];
|
|
769
|
+
State st = s;
|
|
770
|
+
step(st, o[0], o[1]);
|
|
771
|
+
long long sum = evaluate(st);
|
|
772
|
+
rng_state = seed; // 每条候选用同一串随机数,比较才公平
|
|
773
|
+
for (int d = 1; d < SP_DEPTH; ++d) {
|
|
774
|
+
if (st.done) { sum += evaluate(st); continue; }
|
|
775
|
+
Orders q[2];
|
|
776
|
+
for (int j = 0; j < 6; ++j)
|
|
777
|
+
if (st.tank[j].alive) q[j / 3].cmd[j % 3] = rollout_cmd(st, j);
|
|
778
|
+
step(st, q[0], q[1]);
|
|
779
|
+
sum += evaluate(st);
|
|
780
|
+
}
|
|
781
|
+
if (i >= 3) sum = -sum;
|
|
782
|
+
if (best < 0 || sum > best_score) { best_score = sum; best = a; }
|
|
783
|
+
}
|
|
784
|
+
if (nd[i].act[best] != choice[i]) { choice[i] = nd[i].act[best]; changed = true; }
|
|
785
|
+
}
|
|
786
|
+
if (!changed) break;
|
|
787
|
+
}
|
|
788
|
+
Orders out;
|
|
789
|
+
for (int type = 0; type < 3; ++type) out.cmd[type] = choice[tank_id(me, type)];
|
|
790
|
+
return out;
|
|
791
|
+
}
|
|
792
|
+
#endif
|
|
793
|
+
|
|
794
|
+
Orders decide(const State& s, int me) {
|
|
795
|
+
build_maps(s);
|
|
796
|
+
build_tank_maps(s);
|
|
797
|
+
assign_roles(s, me);
|
|
798
|
+
std::memcpy(root_grid, s.grid, sizeof(root_grid));
|
|
799
|
+
// 下一批星星 8 回合内出现:把没有星星的补给点也当作目标,提前去占位。
|
|
800
|
+
soon_mask = 0;
|
|
801
|
+
{
|
|
802
|
+
const int t = s.turn;
|
|
803
|
+
int next = s.P.supply_first;
|
|
804
|
+
if (t >= next) next += ((t - s.P.supply_first) / s.P.supply_period + 1) * s.P.supply_period;
|
|
805
|
+
if (next - t <= 8) soon_mask = ((1 << s.n_supply) - 1) & ~s.star_mask;
|
|
806
|
+
}
|
|
807
|
+
rng_state = 0x9E3779B97F4A7C15ULL ^ (uint64_t(s.turn) * 0x100000001B3ULL) ^ s.wall_hash;
|
|
808
|
+
for (int k = 0; k < 4; ++k) rng();
|
|
809
|
+
|
|
810
|
+
#if SP_METHOD == 1
|
|
811
|
+
return decide_ibr(s, me);
|
|
812
|
+
#endif
|
|
813
|
+
pool_used = 0;
|
|
814
|
+
int root[6];
|
|
815
|
+
for (int i = 0; i < 6; ++i) root[i] = new_node();
|
|
816
|
+
|
|
817
|
+
int steps = 0, sims = 0;
|
|
818
|
+
struct Visit { int32_t node; int8_t act; };
|
|
819
|
+
Visit path[6][SP_DEPTH];
|
|
820
|
+
int path_len[6];
|
|
821
|
+
for (;;) {
|
|
822
|
+
if (steps + SP_DEPTH > SP_BUDGET) break;
|
|
823
|
+
#if SP_USE_TIME
|
|
824
|
+
if ((sims & 15) == 0 && budget_left() < SP_TIME_MARGIN) break;
|
|
825
|
+
#endif
|
|
826
|
+
State st = s;
|
|
827
|
+
int cur[6];
|
|
828
|
+
bool expanded[6] = {};
|
|
829
|
+
for (int i = 0; i < 6; ++i) { cur[i] = root[i]; path_len[i] = 0; }
|
|
830
|
+
int e_sum = 0, depth_done = 0;
|
|
831
|
+
for (int depth = 0; depth < SP_DEPTH && !st.done; ++depth) {
|
|
832
|
+
Orders o[2];
|
|
833
|
+
for (int i = 0; i < 6; ++i) {
|
|
834
|
+
if (!st.tank[i].alive) continue;
|
|
835
|
+
Cmd c;
|
|
836
|
+
if (cur[i] >= 0) {
|
|
837
|
+
Node& nd = pool[cur[i]];
|
|
838
|
+
if (nd.n_acts == 0) build_actions(nd, st, i);
|
|
839
|
+
const int a = select_action(nd);
|
|
840
|
+
c = nd.act[a];
|
|
841
|
+
path[i][path_len[i]++] = Visit{cur[i], int8_t(a)};
|
|
842
|
+
if (nd.child[a] < 0 && !expanded[i] && nd.n[a] > 0) { // 第二次走到这条命令时才往下长一层
|
|
843
|
+
nd.child[a] = new_node();
|
|
844
|
+
expanded[i] = true;
|
|
845
|
+
}
|
|
846
|
+
cur[i] = nd.child[a];
|
|
847
|
+
} else {
|
|
848
|
+
c = rollout_cmd(st, i);
|
|
849
|
+
}
|
|
850
|
+
o[i / 3].cmd[i % 3] = c;
|
|
851
|
+
}
|
|
852
|
+
step(st, o[0], o[1]);
|
|
853
|
+
++steps;
|
|
854
|
+
++depth_done;
|
|
855
|
+
e_sum += evaluate(st);
|
|
856
|
+
if (st.done) e_sum += (SP_DEPTH - depth_done) * evaluate(st); // 提前结束:剩下的回合都按终局分算
|
|
857
|
+
}
|
|
858
|
+
// 一次模拟的分数取沿途每回合评估的平均,而不是只看最后一回合:
|
|
859
|
+
// 这是个拼时间的游戏,"早三回合打到""中途死过一次又复活"都应该体现在分数里。
|
|
860
|
+
#if SP_PATH_AVG
|
|
861
|
+
const int e = e_sum / SP_DEPTH;
|
|
862
|
+
#else
|
|
863
|
+
const int e = evaluate(st);
|
|
864
|
+
#endif
|
|
865
|
+
const float v = float(e) / (float(SP_SQUASH) + float(e < 0 ? -e : e));
|
|
866
|
+
for (int i = 0; i < 6; ++i) {
|
|
867
|
+
const float vi = i < 3 ? v : -v;
|
|
868
|
+
for (int k = 0; k < path_len[i]; ++k) {
|
|
869
|
+
Node& nd = pool[path[i][k].node];
|
|
870
|
+
nd.visits += 1;
|
|
871
|
+
nd.total += vi;
|
|
872
|
+
nd.n[path[i][k].act] += 1;
|
|
873
|
+
nd.sum[path[i][k].act] += vi;
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
++sims;
|
|
877
|
+
}
|
|
878
|
+
total_steps += steps;
|
|
879
|
+
#ifdef SP_DEBUG
|
|
880
|
+
if (me == SP_DEBUG) {
|
|
881
|
+
std::fprintf(stderr, "T%d sims=%d nodes=%d\n", s.turn + 1, sims, pool_used);
|
|
882
|
+
for (int i = 0; i < 6; ++i) {
|
|
883
|
+
const Node& nd = pool[root[i]];
|
|
884
|
+
std::fprintf(stderr, " tank%d Q=%.3f:", i, nd.visits ? nd.total / nd.visits : 0.f);
|
|
885
|
+
for (int a = 0; a < nd.n_acts; ++a)
|
|
886
|
+
std::fprintf(stderr, " %s(p%.2f n%d q%.3f)", cmd_text(nd.act[a]).c_str(), nd.p[a], nd.n[a], nd.n[a] ? nd.sum[a] / nd.n[a] : 0.f);
|
|
887
|
+
std::fputc(10, stderr);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
#endif
|
|
891
|
+
|
|
892
|
+
Orders out;
|
|
893
|
+
for (int type = 0; type < 3; ++type) {
|
|
894
|
+
const Node& nd = pool[root[tank_id(me, type)]];
|
|
895
|
+
int best = -1;
|
|
896
|
+
for (int a = 0; a < nd.n_acts; ++a)
|
|
897
|
+
if (best < 0 || nd.n[a] > nd.n[best] || (nd.n[a] == nd.n[best] && nd.n[a] > 0 && nd.sum[a] > nd.sum[best])) best = a;
|
|
898
|
+
if (best >= 0) out.cmd[type] = nd.act[best];
|
|
899
|
+
}
|
|
900
|
+
#if SP_FFFIX
|
|
901
|
+
// 三辆车的命令是各自在自己的树里选的,偶尔会凑成"一辆开炮、另一辆正好走进炮线"。
|
|
902
|
+
// 出手前用对方最可能的命令前推一回合看一眼:有队友被自己人打中,就把开炮的那辆换成它访问次数次多的非开炮命令。
|
|
903
|
+
for (int round = 0; round < 3; ++round) {
|
|
904
|
+
Orders o[2];
|
|
905
|
+
o[me] = out;
|
|
906
|
+
for (int type = 0; type < 3; ++type) {
|
|
907
|
+
const Node& nd = pool[root[tank_id(1 - me, type)]];
|
|
908
|
+
int best = -1;
|
|
909
|
+
for (int a = 0; a < nd.n_acts; ++a)
|
|
910
|
+
if (best < 0 || nd.n[a] > nd.n[best]) best = a;
|
|
911
|
+
if (best >= 0) o[1 - me].cmd[type] = nd.act[best];
|
|
912
|
+
}
|
|
913
|
+
State st = s;
|
|
914
|
+
EventLog log;
|
|
915
|
+
step(st, o[0], o[1], &log);
|
|
916
|
+
int culprit = -1;
|
|
917
|
+
for (const Event& e : log.list)
|
|
918
|
+
if ((e.type == EV_HIT || e.type == EV_HIT_SWAP) && e.a / 3 == me && e.b / 3 == me && is_fire(out.cmd[e.b % 3])) culprit = e.b;
|
|
919
|
+
if (culprit < 0) break;
|
|
920
|
+
const Node& nd = pool[root[culprit]];
|
|
921
|
+
int best = -1;
|
|
922
|
+
for (int a = 0; a < nd.n_acts; ++a)
|
|
923
|
+
if (!is_fire(nd.act[a]) && (best < 0 || nd.n[a] > nd.n[best])) best = a;
|
|
924
|
+
out.cmd[culprit % 3] = best >= 0 ? nd.act[best] : STAY;
|
|
925
|
+
}
|
|
926
|
+
#endif
|
|
927
|
+
return out;
|
|
928
|
+
}
|