当经典扫雷遇上 Vim 语法:我是如何为开发者写一款“摸鱼”神器的

113 阅读3分钟

一、 为什么又是扫雷?

扫雷是每个程序员的“Hello World”进阶版,但市面上大多数扫雷都高度依赖鼠标。作为一个长期沉浸在终端、习惯 hjkl 的开发者,我一直在想:能不能有一款扫雷,能让我的手指不离开键盘,甚至能玩出 Vim 的“丝滑感”?

于是,zsweep 诞生了。它不仅是一个游戏,更是一个实验场,用来验证如何将 Vim 的操作逻辑(Grammar)完美平移到 Web 交互中。

二、 技术选型:为什么是 Svelte 5?

在开发 zsweep 时,我果断选择了最新的 Svelte 5

  • Runes 的魔力:使用 $state$derived 彻底简化了复杂的棋盘状态管理。在扫雷这种需要频繁触发邻近格子更新的场景下,Svelte 5 的细粒度更新让性能表现极佳。
  • 零负载感:极简的 API 让代码量大幅减少,非常契合项目“极简主义”的初衷。

三、 核心挑战:在浏览器里复现 Vim 体验

为了让 zsweep 拥有真正的“Vim 魂”,我实现了以下特性:

  • 基础移动hjkl 映射。
  • 高效跳转:实现 w/b 跳转(自动跳过已打开的安全区,直达未标记区域)。
// src/lib/game/input/vim.ts

export function handleVimKey(key: string, buffer: string) {
    // 处理数字前缀(用于 5j, 10l 等组合键)
    if (/^[1-9]$/.test(key)) {
        return { type: 'DIGIT', value: key };
    }

    // 核心动作映射
    switch (key) {
        case 'h': return { type: 'MOVE_CURSOR', dx: -1, dy: 0 };
        case 'j': return { type: 'MOVE_CURSOR', dx: 0, dy: 1 };
        case 'k': return { type: 'MOVE_CURSOR', dx: 0, dy: -1 };
        case 'l': return { type: 'MOVE_CURSOR', dx: 1, dy: 0 };
        
        // 单词级跳转:跳过已打开区域,直达下一个未开块
        case 'w': return { type: 'WORD_FORWARD' };
        case 'b': return { type: 'WORD_BACKWARD' };

        // 搜索模式:按 / 进入,跳转至匹配数字的格子
        case '/': return { type: 'START_SEARCH' };
        
        // 游戏核心操作:空格标记/双击效果,回车揭开
        case ' ': return { type: 'SMART_ACTION' }; // 自动判断是 Flag 还是 Chord
        case 'Enter': return { type: 'REVEAL' };
        
        default: return null;
    }
}

在 Svelte 5 组件中,通过 $state 驱动的游标配合上述逻辑,可以实现极其平滑的响应:

<script lang="ts">
    let cursor = $state({ r: 0, c: 0 });
    let vimBuffer = $state("");

    function onKeyDown(e: KeyboardEvent) {
        const action = handleVimKey(e.key, vimBuffer);
        if (!action) return;

        if (action.type === 'MOVE_CURSOR') {
            const mult = parseInt(vimBuffer) || 1;
            // 计算新位置并更新 cursor
            updateCursor(action.dx * mult, action.dy * mult);
            vimBuffer = ""; // 执行完动作清空缓冲
        }
    }
</script>
  • 数字检索:支持按 / 后接数字,快速定位到棋盘上的特定提示数。
  • 组合技:支持数字前缀(如 5j 向下移动五格)。

为了保证操作的即时性,我构建了一套基于状态机的输入处理引擎,确保每一帧输入都能获得 0 延迟反馈。

四、 细节打磨:从 1k+ 活跃用户中学到的

项目上线后,迅速吸引了超过 1000 名活跃用户。在与社区(如 Hacker News, Reddit)的交流中,我不断优化细节:

  1. 数据精度陷阱:早期使用 Supabase 统计全局时间时,遇到了经典的 1000 行分页限制问题,导致统计“漂移”。后来通过 RPC 服务端聚合彻底解决了这一统计 Bug。
  2. UI 的“呼吸感” :参考了 Monkeytype 的审美,加入了极简的主题切换系统,让“摸鱼”也能摸出高级感。
  3. 键盘辅助:在底部加入了实时的按键提示,帮助非 Vim 重度用户也能快速上手。

五、 开源与未来

目前 zsweep 已在 GitHub 完全开源。

目前项目正在推进 Chording(双键消除)移动端手势支持 的特性,欢迎各位掘友来提 PR 或 Issue,一起探讨 Svelte 5 的更多可能性!