# legion-war **Repository Path**: sg-first/legion-war ## Basic Information - **Project Name**: legion-war - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2026-09-24 - **Last Updated**: 2026-10-10 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # legion-war · 军团争霸原型 + AI 数据采集管线 一个回合制多势力夺城的策略小品(网页版),外加一套挂在它上面的**埋点采集管线**: 驱动游戏自动对局,记录「局面 → 决策」轨迹,为 BC(行为克隆)提供离线训练数据。 两部分互相独立:**游戏本体是纯前端、零构建、不需要 Node**;`collector/` 才是需要 Node 的那一半。 --- ## 一、直接玩 双击 `index.html`,或用浏览器打开 `file://<本目录>/index.html`。 侧栏调好参数(势力数 / 城市数 / 成长间隔 / 行动点 / 回合上限),点「开始战局」。 玩法:点自己的军团 → 点邻城 → 进攻;操作完点「结束回合 ⏎」(或按回车),其余势力由 AI 接管。 --- ## 二、采集 AI 训练数据 **前提**(环境不在仓库里,各自本机配好即可):Node 已安装且在 PATH 里;依赖首次装一次: ```powershell cd collector # 或 cd 到本 README 同级的 collector 目录 npm install ``` 三步管线: ```powershell npm run collect # ① 采集 → data/raw_primitives.jsonl npm run stats # ② 体检:分布 / 覆盖 / ε 噪声占比 npm run label # ③ 标注 → data/labeled.csv + data/state_table.csv ``` 或直接 `npm run all`(默认 60 局,一条龙)。 ### 常用参数 | 参数 | 默认 | 含义 | |---|---|---| | `--games` | 10 | 采集局数。**开局期的局面靠加局数补,拉长单局无效**(一局只有一次开局) | | `--factions` | 8 | 势力数,与游戏 UI 默认一致 | | `--cities` | 46 | 城市数,与游戏 UI 默认一致 | | `--growth` | 2 | 数值增长间隔(回合一) | | `--acts` | 2 | 每军团每回合行动点 | | `--turns` | 60 | 回合上限 | | `--out` | raw_primitives.jsonl | 输出文件名 | > 规则参数(势力数 / 城市数 / growth / acts)**跨局固定**,保证所有轨迹遵循同一套规则; > 每局变化的只是地图布局与初始分配。虚拟段位(1~8)仍按局随机分配给各势力,那是策略多样性,不是规则差异。 例:`node src/collect.js --games=300 --turns=60`。 `②③` 可把输入文件当第一个参数传:`node src/adapter_city.js data/xxx.jsonl`。 ⚠️ `collect.js` 每次运行会**清空重写**目标文件。试参数请加 `--out=临时名.jsonl`。 ### 四个文件怎么分工 | 文件 | 跑在哪 | 干什么 | |---|---|---| | `src/instrument.js` | **浏览器里** | 挂钩 `moveLegion` 记录完整局面;8 档虚拟段位策略;绕过动画自己驱动回合 | | `src/collect.js` | Node | 起 headless Chromium,反复开局并写 JSONL | | `src/adapter_city.js` | Node | 原始数据 → 21 维特征 + 7 类意图(**改规则只重跑这里,不用重采**) | | `src/stats.js` | Node | 体检:分布、覆盖、负面样本比例 | ### 为什么分两层落盘 ``` 跑游戏(60 局实测约 30 秒) → raw_primitives.jsonl ← 采一次反复用 离线标注(便宜,几秒) → labeled.csv state_table.csv(局面 → 7 动作分布) ``` 特征计算和意图标签的规则一定会反复改,所以「采集」和「标注」必须拆开。 ### 游戏目录怎么找 代码里**没有任何硬编码绝对路径,也不依赖环境变量**,全部按脚本位置相对推导: 埋点目录的上级 → 上两级 → `../legion-war`。找不到 `index.html` 时直接报错,不静默回退。 ### 原始数据字段 四种记录,每行一个 JSON: - `kind: "map"` —— 开局地图快照(城市邻接关系、归属、初始段位分配) - `kind: "decision"` —— 一次军团移动决策:`legion`(兵力/上限/剩余行动点)、`from`、`fromTroops`、 `cands`(**所有候选邻城**的归属/守军/防御值/驻敌)、`targetId`(实际选了谁)、 `prevTarget`(上一步的**进攻意图**目标:打异己的城就记,无论成败;stay/回防清空。游戏本身不记录)、`g`(全局:回合/占城/兵力/存活势力)、 `before`/`after`(易主、掉血、是否被歼)、`policyTier`(虚拟段位)、`explore`(是否 ε 探索) - `kind: "stay"` —— 「没移动」也是一次决策,必须记录,否则模型学不到何时该停 - `kind: "turn_end"` —— 回合快照,用于后期算 reward 代理 --- ## 三、目录结构 ``` legion-war/ ├── index.html 游戏入口,双击即玩 ├── js/ 游戏本体源码(13 个 classic script,无构建) │ ├── config.js 常量:画布尺寸、势力色/名、城市名素材 │ ├── utils.js 纯函数工具:rnd / sleep / shuffle / 几何 │ ├── state.js ★ 唯一可变状态持有者 S + 访问器 │ ├── mapgen.js 地图生成:撒点 + 无交叉边 + 连通性补全 │ ├── setup.js newGame():势力/归属/初始守军与军团分配 │ ├── combat.js 战斗内核:defenseOf / moveLegion / capture / spawnLegion │ ├── turn.js 回合结算:upkeep / growAll / resetMoves / endTurn │ ├── ai.js AI 决策:贪心打分 + BFS 推边境 │ ├── endgame.js 胜负判定与结算弹窗 │ ├── render.js Canvas 绘制 │ ├── ui.js 侧栏/顶栏/日志 DOM 刷新 │ ├── input.js 鼠标选中/进攻、按钮、滑块 │ └── main.js 启动:注入空局并首次绘制 └── collector/ AI 数据采集管线(需要 Node,与游戏本体互不依赖) ├── package.json └── src/ instrument.js / collect.js / adapter_city.js / stats.js ``` --- ## 四、游戏规则 **实体**:地图是一张「城市 + 无向边」组成的连通图。城市有归属与守军;每势力固定 2 支军团,有兵力值。 **一局的流程**: ``` 选中军团 → moveLegion 进攻邻城 → checkEnd 判定 点「结束回合」: AI 逐个势力行动 → 成长(每 growth 回合全军壮大)→ upkeep(回血/重建/淘汰) → 回合数 +1 → 重置行动点 → 判定胜负 ``` **战斗结算**:攻方兵力 vs 守方(城内异己军团兵力 + 守军)。胜则守军全灭、易主、守军刷为上限五成;败则攻方按守方防御值受损。 损耗不是永久的,回合末按上限比例回复;军团阵亡数回合后在本方守军最多的城满编重建。 **胜负**:己方失全部城市即败;敌对势力全灭或占满全图即胜;回合用尽则按占城数(并列比兵力)排名。 --- ## 五、给二次开发者的接口提示 脚本全是 classic script,顶层 `function` 声明自动挂到 `window`,可被注入脚本直接调用, **不需要改动游戏源码**就能自动跑局: - `newGame(n, m, growth, acts, maxTurns)` —— 绕过设置面板直接开局 - `moveLegion(l, targetId)`(`js/combat.js`)—— 第一个参数 `l` 是军团**对象引用**而非 id(游戏源码里沿用单字母命名);内部不校验邻接与行动点,调用方需自行保证合法 - `defenseOf` / `checkEnd` / `upkeep` / `growAll` / `resetMoves` / `finishByScore` / `aiStep` / `bfsStep` ⚠️ 全局状态是 `let S`,**不在 `window` 上**(`window.S` 是 undefined)。在 `page.evaluate` 里用裸标识符 `S` 访问: `page.evaluate(() => S.factions.map(f => f.id))`。同理还有 `busy`、`selLegion`。对局结束标志是 `S.over`。 ⚠️ 想绕过动画高速跑局,**不要调 `endTurn()`**(内含 `sleep(190)`)。按 `collector/src/instrument.js` 里的 `runTurn()` 用同步函数复刻一回合即可,快百倍。 --- ## 六、已知偏差(读数据时务必记住) 1. **21 维特征里有 3 个是代理量**,原型游戏没有对应概念: `GamePhaseType`(按中立城比例推的阶段)、`EconomyLevel`(按占城比例分档)、`Situation`(按城数/兵力领先与否构造)。 真实定义到位后需替换。 2. **虚拟段位**:原型里没有真人玩家,所以埋点用 8 档参数化策略模拟不同水平(含 ε 探索)。 它只能近似「水平差异」,不等于真人段位习惯。 3. **意图标签严重不均衡**(`FullAtkWeakestNonAllyNeighbor` 常占四成以上),训练前需重采样或加权损失。