dsh-whale-arcade
浏览器本地运行的悬浮鲸鱼游戏中心,包含三款积分游戏和海洋主题五子棋,适合等待 Agent 时随手游玩。
安装
dsh plugin --profile web add dsh-whale-arcade
沙箱测试结果
| 阶段 | 结果 |
|---|---|
| 1 安装 | ✗ |
| 2 列装 | — |
| 3 挂载 | — |
| 4 启动 | — |
dsh: initialized profile web at /home/node/.dsh/profiles/web四阶段在隔离沙箱里跑:安装(有网)→ 列装 → 挂载 → 启动(后三步断网)。
README
dsh-whale-arcade
[English](README.md) | 中文
在等待模型响应、工具执行或后台任务完成时,用四款轻量小游戏填补短暂的等待时间;随开随玩,关闭后立即回到工作,不进入会话,也不影响 Agent 继续执行。
<p align="center">
<img src="https://raw.githubusercontent.com/jitengfei/dsh-whale-arcade/main/assets/whale-arcade-banner.png" alt="鲸鱼游戏中心海洋横幅" width="100%">
</p>
dsh-whale-arcade 是挂载在 DeepSeek Harness 全局 shell.overlay 的浏览器小游戏插件。右下角的小鲸鱼是常驻入口;浮层只占据工作台一角,关闭后游戏会暂停并保留现场,不会中断正在运行的 Agent。
当前游戏
游戏 · 玩法 · 操作
鲸鱼跃浪 · 在水下洞口间穿行;洞口大小、高度变化、速度和反应距离会逐步变化 · 点击、触摸、空格、↑、W
蓝鲸寻宝 · 横向移动接取海星、小鱼、螃蟹和稀有珍珠贝,避开水母与海胆;不同生物有不同分值和速度 · ←、→、A、D、可长按触屏按钮
鲸跃海岸线 · 越过海螺、海胆、珊瑚塔和沉船残骸;包含阶段解锁、低障碍组合以及短、中、长三档随机间距 · 点击、触摸、空格、↑、W
鲸海五子棋 · 在 15×15 潮汐棋盘上使用蓝鲸棋子,与本地白鲸对手进行自由五子棋对局;玩家先手,横、竖或斜线连续至少五子获胜,不设禁手;支持轻松、标准、挑战三档难度 · 点击或触摸落子,键盘方向键移动焦点
四个游戏均支持开始、暂停、继续和结束后重开。前三款积分游戏会在目录中显示保存在当前浏览器站点(origin)的前十名成绩;同分时按实际游玩用时和达成时间排序。五子棋不写入排行榜;棋盘与所选难度只存在于当前已挂载的游戏中,关闭浮层会暂停并保留现场,返回游戏目录或刷新页面则会重置。手动暂停、关闭浮层或隐藏浏览器标签页时不会累计游玩时间。
安装
安装前请确认 DeepSeek Harness 能通过 dsh web 启动,并且 pnpm 位于 PATH 中。Harness 的插件命令会调用 pnpm。推荐直接安装 npm 中的预构建包:
dsh plugin --profile web add dsh-whale-arcade dsh web
如果 dsh web 已经在运行,请先停止旧进程,再重新启动并刷新浏览器。打开终端打印的 Web 地址;右下角出现鲸鱼入口即表示安装成功。插件自带 cordis.patch.yml,无需手工修改 Harness 配置。
还没有 dsh 时,请先按 DeepSeek Harness 官方说明 运行 Harness;使用 npx 的用户也可以执行:
npx @deepseek-ai/dsh@0.1.0-rc.6 plugin --profile web add dsh-whale-arcade npx @deepseek-ai/dsh@0.1.0-rc.6 web
当前代码已在 DeepSeek Harness 0.1.0-rc.6 上完成安装与运行验证。Harness 仍处于开发者预览阶段,后续版本可能包含不兼容变更。
如果需要测试尚未发布到 npm 的最新提交,请把包参数换成 github:jitengfei/dsh-whale-arcade。
更新或卸载
dsh plugin --profile web update dsh-whale-arcade dsh plugin --profile web remove dsh-whale-arcade
更新或卸载后同样需要重新启动正在运行的 dsh web。
只通过 npx 使用 Harness 时,请把上述每条命令开头的 dsh 替换为 npx @deepseek-ai/dsh@0.1.0-rc.6;源码开发段同理。
从源码开发
需要 Node.js 22.19+(仅限 22.x)或 24+,以及 pnpm 11.7.0:
git clone https://github.com/jitengfei/dsh-whale-arcade.git cd dsh-whale-arcade corepack enable corepack prepare pnpm@11.7.0 --activate pnpm install --frozen-lockfile pnpm run check dsh plugin --profile web add . dsh web
pnpm run check 会依次执行类型检查、测试、构建和发布产物校验。启动时请打开终端实际打印的地址。
扩展游戏
这套扩展机制用于在本仓库源码中增加随插件一起编译的内置游戏。src/client/game-registry.ts 是编译期内部注册表,不是面向外部包的动态子游戏 API;第三方包不能在运行时向已构建的插件注入游戏。普通新增只需实现游戏定义并加入注册表,不应为某个游戏在 Shell 中增加专用渲染分支。
最小目录
src/client/games/tide-puzzle/ ├── TidePuzzleGame.tsx ├── definition.tsx ├── model.ts └── TidePuzzleGame.module.css
TidePuzzleGame.tsx 和 definition.tsx 是最小必需文件。规则、物理、关卡生成或 AI 应尽量拆成可独立测试的纯 TypeScript 模块;游戏自己的布局样式也应尽量留在该目录。只有两个以上游戏真正共享的视觉或运行能力才应上移到 src/client/shared/ 或 src/client/runtime/。
定义并注册游戏
definition.tsx 把游戏接入目录、通用浮层、生命周期和记录系统。一个不保存成绩的最小定义如下:
import type { GameDefinition, GameIconProps } from '../../runtime/game-contract.ts'
import { NO_RECORD_POLICY, type RecordPolicy } from '../../runtime/records.ts'
import { TidePuzzleGame } from './TidePuzzleGame.tsx'
function TidePuzzleIcon({ className }: GameIconProps) {
return <span className={className} aria-hidden="true">◌</span>
}
export const tidePuzzleGame = {
id: 'tide-puzzle',
nameKey: 'tidePuzzle.name',
descriptionKey: 'tidePuzzle.desc',
Icon: TidePuzzleIcon,
View: TidePuzzleGame,
initialHud: { primary: { id: 'moves', labelKey: 'moves', value: 0 } },
recordPolicy: NO_RECORD_POLICY,
} as const satisfies GameDefinition<'tide-puzzle', RecordPolicy>
字段 · 作用
id · 全局唯一且稳定的编译期 ID,也是本地记录的分区键;发布后不要改名
nameKey、descriptionKey · src/client/locales.ts 中同时存在的中英文文案键
Icon、View · 目录图标与游戏画面组件
initialHud · 每局开始时的通用状态栏;最多两个数值槽和一个状态文案
Setup、defaultVariantId · 可选的开局设置与默认规则标识
recordPolicy、recordLabelKey · 必需的记录策略,以及排行榜主指标的可选文案键
随后在 src/client/game-registry.ts 导入定义,并只在有序 GAMES 数组中追加一次:
import { tidePuzzleGame } from './games/tide-puzzle/definition.tsx'
export const GAMES = [jumpGame, catchGame, runnerGame, gomokuGame, tidePuzzleGame] as const satisfies readonly ArcadeGameDefinition[]
目录、游戏浮层和排行榜导航都从 GAMES 派生。还需要在 src/client/locales.ts 同时补充中文和英文文案;不要在 WhaleArcade.tsx、GameCatalog.tsx、GameFrame.tsx 或 GameRecords.tsx 中按游戏 ID 写 switch 或条件分支。
生命周期契约
游戏的 View 接收 GameViewProps,并在四个阶段中保持挂载:
phase · 游戏应做什么
ready · 显示初始画面并等待 Shell 的开始操作;若有 Setup,它会覆盖在画面上
running · 处理输入、动画帧、计时器和本地 AI,并通过 updateHud() 更新通用状态栏
paused · 完全冻结推进和输入,保留现场;关闭浮层或隐藏浏览器标签页也会进入此阶段
finished · 停止推进并保留终局,等待 Shell 重开
必须遵守以下规则:
runId是一局游戏的世代标识。选择游戏、开始、重开或放弃都会使它变化;组件应在runId变化时重置自己的 state、ref、棋盘、物理世界和终局锁。- 只有
phase === 'running'时才能推进游戏。连续动画优先使用useGameLoop(phase === 'running', tick);timeout、本地 AI、全局事件监听和按键集合也必须在暂停、换局或卸载时清理。 updateHud()和finish()已绑定当前runId,在暂停、结束或陈旧一局中调用会被拒绝。finish()返回boolean;只有返回true时,游戏才应设置自己的ended标记或播放一次性终局效果。finish()的outcome可以是completed、failed、won、lost或draw;metrics只能包含有限数值,例如score、moves、level或progress。- View 通过
translate(key, params)使用插件命名空间中的双语文案;游戏内可见文字和 ARIA 标签都不要硬编码单一语言。 - 有效用时由 Shell 的 session 和 active timer 管理。游戏不要另建一套开始、暂停、继续或成绩提交状态机,也不要自行调用
recordGameResult()。
Setup 与 variantId
需要难度、先后手或规则选择时,可以在 definition 中提供 Setup。它只在 ready 阶段显示,并通过 selectVariant() 选择一个稳定字符串;开始后调用会返回 false。多个设置可以编码成规范且向后兼容的字符串,例如 hard-player-first,游戏自身仍需为未知值提供安全回退。
variantId 只锁定当前 session 中这一局采用的规则,并附加到完成结果以隔离不同规则的记录;它不是磁盘设置。返回目录后再次进入会使用 defaultVariantId,刷新页面也不会恢复选择或在途棋局。需要在运行中显示的复杂控制应放在游戏画面内,不应扩充通用顶部栏;顶部栏刻意只提供 primary、secondary 两个数值槽和一个 statusKey。
记录策略
游戏只负责调用 finish(),Shell 会根据 definition 的策略统一写入当前 origin 的 localStorage:
策略 · 持久化 · 现成 Shell 展示
NO_RECORD_POLICY / kind: 'none' · 不写入 · 无最佳值或记录列表
HIGH_SCORE_POLICY / kind: 'leaderboard' · 按 rankBy、limit 和可选 outcomes 保存 · 目录最佳值、顶部最佳值和排行榜
kind: 'history' · 按时间倒序保存有限条历史 · 暂无通用历史界面
自定义排行榜的第一条 rankBy 规则决定 Shell 展示的主
…(truncated)
以上内容来自 npm 上的 README,我们只做格式化,不改内容。
版本
npm 上共发布:1,最后更新 2026-08-15