冲上了 Hacker News 第 5 名,竟然是我的 Svelte 练手项目
今天早上醒来,发生了一件让我有点懵的事情。我前段时间为了学习 Svelte 而写的一个“练手项目”—— Zsweep,竟然冲上了 Hacker News (HN) 首页的第 5 名。
(这是后面看到时的截图,最开始上了前5,可惜没有截屏😭)
![]()
(此图为证)
![]()
(小站下午游戏时长暴涨100小时🤯)
![]()
对于独立开发者来说,HN 的首页就像是“奥斯卡红毯”。看着自己写的代码被全球各地的极客讨论, 我想趁热打铁,在掘金复盘一下这个项目的开发思路、技术栈选择,以及我为了让它“好玩”而死磕的一些技术细节。
HN:news.ycombinator.com/item?id=466…
Repo: github.com/oug-t/zswee…
🎮 什么是 Zsweep?
简单来说,Zsweep 是一个 Vim 键位驱动的扫雷游戏。
它的灵感来源有两个:
- Monkeytype:我很喜欢 Monkeytype 那种极简、无广告、纯粹追求速度的打字体验。
-
Vim/Neovim:作为一名开发者,我想把
hjkl的肌肉记忆延伸到游戏里。
所以 Zsweep 的设计哲学就是:极简 UI + 极致手速 + 全键盘操作。
![]()
🛠️ 技术栈:为什么选择 SvelteKit + Supabase?
作为一个全栈项目,我没有选择我最熟悉的 React,而是选择了 SvelteKit,搭配 Supabase。
1. 前端:SvelteKit + Tailwind CSS
Svelte 真的太爽了。在这个项目里,我深刻体会到了“Write less code”的含义。
- 状态管理:不需要复杂的 Context 或 Redux,Svelte 的响应式变量让处理游戏状态(比如计时器、剩余雷数、当前选中的格子)变得异常简单。
-
动画:Svelte 内置的
transition和animate指令,让我几行代码就实现了“踩雷”时的屏幕震动和结算界面的数字跳动效果。 - Vim 键位绑定:我写了一个全局的键盘监听器,配合 Svelte 的 store,实现了丝滑的光标移动体验。
2. 后端 & 数据库:Supabase
因为是独立开发,我不想花时间在配运维环境上。Supabase 提供了 PostgreSQL 数据库和开箱即用的 Auth(认证)服务。
- 登录:直接集成了 GitHub 和 Google OAuth,几行配置就搞定。
- 排行榜:利用 Postgres 的强大查询能力,我能很快算出全球排名。
💻 那些让我“掉头发”的技术细节
虽然是扫雷,但为了追求极致体验,我在数据处理上花了不少心思。
1. 核心算法:Mines/Min (扫雷效率)
传统的扫雷只看时间,但不同难度的雷数不一样。为了衡量玩家的真实水平,我参考了 Monkeytype 的 WPM (Words Per Minute),设计了 Mines/Min (每分钟扫雷数) 指标。
(也implement了3BV,但考虑到time mode,还需后续更新)
这里有个坑:如果是通过点击复位(重开)太快,可能会导致除以零或者时间极短的数据异常。 我在前端加了一个健壮的计算逻辑:
TypeScript
// 核心计算逻辑片段
if (timeTaken > 0) {
const minesPerMin = parseFloat(((mines / timeTaken) * 60).toFixed(1));
// 只有当成绩更优时才更新本地的最佳记录
if (!calculatedBests[cat] || minesPerMin > calculatedBests[cat].value) {
calculatedBests[cat] = {
value: minesPerMin,
date: g.created_at
};
}
}
2. 全球排行榜与“防作弊”
为了做 Leaderboard,我利用 Supabase 的 Foreign Key 把 game_results 表和 profiles 表关联起来。
刚才上线后发现一个小插曲:数据库里出现了一些 0秒 的通关记录(大概是调试时的残留数据,或者是 API 被人用 Postman 刷了)。
为了保证公平,我在后端查询时加了严格的过滤器,利用 SQL 直接过滤掉异常数据:
TypeScript
const { data } = await supabase
.from('game_results')
.select('time, profiles(username)')
.eq('win', true)
.gt('time', 0) // 过滤掉 0s 的异常数据
.order('time', { ascending: true })
.limit(50);
现在,排行榜终于干净了,还能显示“Your Rank”高亮自己的排名。(下一个PR,的ploy)
3. 用户体验细节
- Glitch 风格:当踩雷失败时,我没有用普通的弹窗,而是写了一个 CSS Glitch(故障风)特效,配合 "FATAL_ERR" 的文案,更有极客感。
- 热力图:参考 GitHub Contribution,我在个人主页做了一个扫雷热力图,记录玩家每天的活跃度。
🚀 总结与开源
这次冲上 Hacker News 第 5 名,给我最大的启示是:不要等到项目完美了才发布。
Zsweep 其实还有很多 Issues(比如之前的 Joined Date 显示 Invalid Date,刚刚才修好 😂),UI 也不够完美。但因为它解决了一个小痛点(想用 Vim 玩游戏),并且做得足够简单纯粹,就获得了很多开发者的喜爱。
目前项目完全开源,如果你对 Svelte、Vim 或者扫雷感兴趣,欢迎来 GitHub 给个 Star,或者提 PR 一起改进它!
- Live Demo (试玩) : zsweep.com
- GitHub 源码: github.com/oug-t/zswee…
如果你也喜欢 Vim 或者 Svelte,欢迎在评论区交流!