bihong2026/tankbattle--plugins-tank-battle ↗★ 0
dsh-tank-battle
打坦克:在侧栏开一个页面,红白机经典打坦克(Battle City)。单人/组队打 NPC,也可以各带一队 NPC 互相对战,四档 AI 随时换,带在线用户发现与积分榜 适合桌面端或 Web 端用户在侧栏进行单人、组队或对战坦克游戏。
インストール
npx -p @deepseek-ai/dsh dsh plugin --profile web add github:bihong2026/tankbattle#1822e0a65457ee578a1de1939e6bb0ed6e1f3eee&path:plugins/tank-battleドキュメント
README 全文を読む ↗DSH 打坦克插件
红白机经典打坦克(Battle City)娱乐插件。单人打 NPC,也能发现同时在线的其他玩家、 邀请组队一起打,或者各带一队 NPC互相对战。装好之后侧栏会多出一项「打坦克」, 点开就是一整页游戏,随时开打、随时结束,还带积分榜。
游戏本体纯浏览器端跑,不依赖任何第三方库,也不改官方任何一个文件。联机部分靠一台打坦克
后台服务(见仓库里的 server/),自己部署一台就行。
安装
桌面版(DeepSeek Harness)没有可用的命令行安装方式:dsh 启动器对 desktop 这个
profile 会直接报错(「managed exclusively by the desktop application」),它由桌面应用
独占管理。所以走界面装:
- 打开应用 → 插件 → 添加插件;
- 粘贴下面任意一种地址,点安装;
- 完整退出应用再启动(关窗口不算,要从托盘退出)——宿主半要换新版才会带上前端要的那几条路由。
| 形式 | 填什么 |
|---|---|
| Git(推荐) | https://github.com/bihong2026/tankbattle.git#path:plugins/tank-battle |
打不开 GitHub 就用 https://gitee.com/bihong2023/tankbattle.git#path:plugins/tank-battle | |
| 本地目录 | 把仓库克隆到本地后,填 plugins/tank-battle 这个目录的绝对路径(例如 D:\code\tankbattle\plugins\tank-battle) |
| 包名 | dsh-tank-battle |
装完后侧栏会出现「打坦克」。版本号显示在页面右上角;如果页面提示「请完整退出应用再启动」, 说明前端半已经换了、宿主半还是旧的,退出重开即可。
命令行安装(web / headless / sdk / acp 等 profile)
非桌面 profile 可以直接用 dsh plugin(它会把参数转发给 pnpm,所以 pnpm 要在 PATH 上):
# 直接从 GitHub 装(Gitee 镜像把 github.com/bihong2026 换成 gitee.com/bihong2023 即可)
dsh plugin --profile
add "git+https://github.com/bihong2026/tankbattle.git#path:plugins/tank-battle"
# 或者先克隆再装本地目录
dsh plugin --profile
add ./plugins/tank-battle
dsh plugin --profile
remove dsh-tank-battle
增删组合包后必须重启那个 profile 才会生效。桌面版的 desktop profile 不接受命令行安装,
只能走上面的界面。
怎么玩
| 操作 | 按键 |
|---|---|
| 上下左右 | ↑ ↓ ← →(或 W A S D) |
| 开火 | 空格(或 J / Enter) |
| 暂停 / 继续 | Esc(或 P) |
嫌键盘不方便也可以用页面上的方向键和「开火」按钮(按住即可持续)。
- 砖墙一发打穿一格;升到三星的子弹能打穿钢墙。
- 打死特定敌兵会掉道具(星、头盔、时钟、铁锹、手雷、额外命),吃到就生效。
- 单人/组队:基地被毁或我方生命耗尽即结束。
- 对战:打掉对面基地就赢,自家基地没了就输;兵打光了还没人掉基地就按比分定胜负。
三种模式
| 模式 | 说明 |
|---|---|
| 单人 | 一辆坦克打 NPC,人机对战。 |
| 组队 | 你和队友都在蓝队,一起打中立的 NPC(灰)。子弹只伤非同队,所以不会误伤队友。 |
| 对战 | 你(蓝队 A)和对手(红队 B)各带一队 NPC 互相攻防,两边各有一个基地。 |
联机流程
- 进页面就是大厅:能看到在线的人、收到的邀请、别人开的公开房间、以及积分榜。
- 点某人后面的「组队」或「对战」发邀请;对方在大厅点「接受」就成房。
- 也可以自己「开组队房 / 开对战房」,把房间号发给对方让他「加入」。
- 点「开始游戏」。房主权威:房主跑真正的游戏引擎,按节流把画面推给客人; 客人只把按键传过去、照着房主的画面渲染。所以场上永远只有一份真值,两边不会跑偏。
- 打完之后积分由服务端算(客户端只上报结果),记进积分榜。
身份
联机要有个名字挂在积分榜上,插件按两条来源取:
- 登录的 DeepSeek 账号 —— 走 Harness 自带的账号服务,取稳定账号 UID。 换机器、重装 Harness,只要还是同一个账号,积分和战绩就还是连着的那一份。
- 游客 —— 没登录就用一个按「harness home」生成的本机游客号(
guest-开头)。 游客一样能发现人、能邀请、能开房、能上榜,只是名字是「游客 XXXX」而不是你的账号。
两种身份都在宿主半读取,凭证一个字都不出那个进程,前端拿不到也传不出去。 所以单人玩法完全不需要登录,不联网、不装后台服务也能玩。
敌方 AI
开局前可以在页面上选,开局后也能随时换,下一帧就生效:
| 档位 | 说明 |
|---|---|
| 新兵 | 乱走乱打,偶尔摸到基地 |
| 老兵 | 直线逼近基地和玩家,对上了就开火 |
| 猎手 | 会绕路、会卡位、看到子弹会闪 |
| 王牌 | 绕路包抄、预判走位、集火基地 |
猎手和王牌用 Dijkstra 在「地形代价场」上寻路,所以会主动绕开钢墙和河,不会一根筋撞墙。
页面上的设置
- 模式:单人 / 组队 / 对战。
- 敌方 AI:四档,见上。
- 敌人总数:10 / 20 / 30 辆。
- 我方生命:1 / 3 / 5 / 9 条。
- 起始关卡:第 1–4 关(四张手写地图)。
- 音效:开关。音效是现场用 WebAudio 合成的,不带任何音频文件。
这些选择只作用于当次,不写盘。
插件设置(默认值)
cordis.patch.yml 的 config 里放开局默认值和后台服务地址(改完要重启应用才生效):
config:
defaultAi: 'veteran' # rookie / veteran / hunter / ace
enemyCount: 20 # 4 到 60
lives: 3 # 1 到 9
sound: true
serverBase: 'http://122.51.206.118/tanks' # 联机用的后台服务;默认是作者架好的公网服务
装好之后不改也能玩:单人模式跟后台无关;联机也开箱可用,因为默认地址指向仓库作者 公网部署的那一份服务。前端每次开局先向宿主半要一遍这几个值当默认。
想连自己架的服务(或者连别人架好的),别改包里的文件 —— 在
$DSH_HOME/cordis.patch.yml(默认 ~/.dsh/cordis.patch.yml,这一层在每个 bundle 之后生效、
且对所有 profile 生效)里覆盖这个插件的 serverBase 就行:
- id: tank-battle
config:
defaultAi: 'veteran'
enemyCount: 20
lives: 3
sound: true
serverBase: 'http:///tanks' # 末尾不要斜杠,宿主半会自己接 /api/...
patch 的
config是整体替换而不是深合并:只写serverBase也不会出问题(没写的键会 回落到代码里的默认值,和上面那份一致,自检会核对),但把五个键都写上更不容易看糊。
也可以直接设环境变量 TANK_SERVER_BASE,它的优先级最高。三种来源的优先级是
环境变量 → 插件设置 → 代码默认值。
默认为什么指向作者的服务器:绝大多数装插件的人不会在自己机器上架一个后台,把默认留成
http://127.0.0.1:9010 的结果是每个人一进联机就报「连不上打坦克后台服务」——这是
作者踩过的坑。默认指向已部署好的公网服务,两个人各装一个插件就能直接凑一桌。
自己架服务、不想用作者那台,按上面的办法覆盖一行即可;怎么把服务架到公网、怎么在既有
80 端口上做路径反代,见 server/README.md 的「放到公网」。
宿主半的路由
四条精确路由加一条前缀反代,全部挂在官方的 dsh-host-webserver 上,与页面同源。桌面端由
dsh-app:// 协议整体转发到宿主,所以 Web 形态和桌面形态共用同一份前端代码,也就不用让
Electron 外壳再开 IPC 通道。
| 路由 | 作用 |
|---|---|
/tank-battle/ping | 探活 + 版本自检,顺带把插件设置里的默认值发给前端 |
/tank-battle/stats | 读本机战绩($DSH_HOME/tank-battle.json) |
/tank-battle/record | 记一局的结果:分数、打到第几关、哪一档 AI、结果 |
/tank-battle/identity | 报本机身份(登录的账号 / 游客),联机时拿它当名字 |
/tank-battle/api/*(前缀) | 反代到打坦克后台服务,在线发现 / 邀请 / 房间 / 积分都走这条 |
反代必须走宿主,不能让前端直接连后台服务:页面自己的来源不是后台服务,浏览器会拦(跨域, 桌面端还会判混合内容);身份也一样,只有宿主半拿得到。
后台服务连不上时反代回 {ok:false, message:"连不上打坦克后台服务(…)"},大厅就是空的,
单人玩法一点不受影响。
战绩是「读—合—写」,脏数据一律收拾成 0 而不是 NaN;文件坏了就退回空战绩,不抛错。
前端半和宿主半版本对不上时(换包后宿主没重启),页面会直接提示「请完整退出应用再启动」。
文件
client.js 浏览器半:引擎 + 绘制 + 大厅 + 那一页(只 require("react"),样式只用 --dsw-alias-* 令牌)
index.js 宿主半:注册四条同源路由 + 一条反代前缀路由
identity.js 本机身份:登录的 DeepSeek 账号,退回游客(只读,不写凭证)
tank-stats.js 战绩落盘($DSH_HOME/tank-battle.json)
cordis.patch.yml 打包补丁:插件的默认设置与后台服务地址
icon.svg 图标(像素坦克 + 砖纹)
preview.html 不装插件也能打开看/玩的单张页面(直接引同一个 client.js,用真实引擎)
locale/zh.json 中文标题与描述
locale/en.json 英文标题与描述
test/run.mjs 纯逻辑 + 路由契约 + 身份分支 + 多人(座位表 / 出生点 / 快照 / 击杀计数)+ 真实对局自检
test/render.mjs 页面行为自检(假 React / 假画布 / 假后台服务,把整页真跑起来)
test/preview.mjs preview.html 的自检(假 DOM 把整页真跑一遍,顺带核对页面读的每个 id 都存在)
先看一眼再装
preview.html 是给「不想先装插件、只想看看长什么样」用的:双击打开即可(file:// 也能跑,
页面不依赖 fetch),三种模式、四档 AI、屏幕上的方向键都能用;组队/对战时二号位用
I J K L + U 开火。它直接引 client.js 里的同一套引擎,不是另抄一版,所以看到的
就是插件里那个画面。
自检
三套自检都不联网、不依赖第三方库、不开真浏览器,直接用 node 跑:
node test/run.mjs # 引擎、地图、四档 AI、完整对局、路由契约、身份分支、反代、座位表、出生点、快照、战绩写盘
node test/render.mjs # 首屏 → 大厅 → 邀请 → 接受 → 开房 → 开始 → 操作 → 暂停 → 结束 → 上报战绩
node test/preview.mjs # 预览页:首屏 → 单人(操作/暂停/结束)→ 组队两套键 → 对战双队比分
三套都全过才算这版能交。写盘的用例跑在子进程里、并把 DSH_HOME 指到临时目录,
不会动你本机那份 tank-battle.json。
身份那一段不用真去登录:test/run.mjs 拿假账号服务把「登录 / 没登录 / 服务不存在 /
服务自己报错 / 资料里没有 ID」几条分支全撞一遍。
联机那一段还有一份对真实后台服务的契约自检,在 server/test/client_contract.py
(只用 Python 标准库,字段名全部抄自 client.js,改契约会先红):
python3 ../server/test/client_contract.py http://127.0.0.1:9010
后台服务
放在仓库的 server/ 下,是一份独立的 Flask + MySQL 小服务(systemd + gunicorn 托管,
默认监听 9010)。装法、接口清单、容量压测和自检见 server/README.md。
不装它也能玩:单人和四档 AI 都在浏览器里跑,联机功能会安静地降级成「大厅是空的」。