ToolAct工具行动

见缝插针小游戏

把针射入旋转的圆盘,避免针针相撞,挑战 10 关速度极限

1
当前关卡
10
总关卡数
8
剩余针数
1
历史最佳
第 1 关 / 共 10 关
剩余针数:8
8

什么是见缝插针小游戏?

见缝插针是一款节奏简单但越往后越紧张的休闲小游戏:屏幕中央是一个匀速旋转的白色圆盘,你需要从下方依次把针发射出去,让针稳稳钉在圆盘上,并且不能撞到已经钉住的针。每关给定固定数量的针,全部成功钉入即过关,任何一次相撞都立刻失败。它的原型是手机端非常出名的 AA、Pin Out 这类小游戏,玩法极简,看似随便点点就能玩,真正上手后却很考验时机判断和心理控制。本工具把它搬到浏览器里,无需注册、不要下载,电脑用鼠标点击,手机用手指轻点屏幕即可发射。

使用方法

操作步骤

  1. 页面打开后游戏会自动进入第 1 关,圆盘开始匀速旋转
  2. 点击游戏区域(或按空格 / 回车键)发射一根针
  3. 针朝中心飞行并钉入圆盘,需要确保不撞到圆盘上已有的针
  4. 把本关所有针全部安全钉入即过关,自动进入下一关
  5. 任何一次相撞都判定失败,可以选择「重玩本关」或「重新开始」(注意:失败后按空格 / 回车会触发「重新开始」回到第 1 关,想重玩本关需要点按钮)

通关小窍门

  • 圆盘是匀速旋转的,找节奏比靠手速更重要:盯着空隙的中线再点。
  • 看到旁边针刚刚扫过的瞬间发射,针飞行需要约 120ms,给点提前量。
  • 可以连续点击连发,最多缓冲 3 发依次飞出,超出会被丢弃,所以前一发未落地前不必狂点。
  • 越往后剩余空位越窄、转速越快,宁可少一次试探也别贪点。

关卡难度说明

  • 第 1-3 关:针多、空隙大,主要熟悉操作节奏。
  • 第 4-7 关:圆盘已有针变多,需要预判转速。
  • 第 8-10 关:圆盘几乎被插满,每根针都得算准时机。

使用场景

工作或学习的碎片时间放松一局完整 10 关大约只需 3-5 分钟,输了重开几乎没有惩罚,非常适合开会前几分钟、写完一段代码后想换换脑子的间隙。游戏画面只有黑白两色,没有广告弹窗,也不会发出突兀的音效,开着浏览器在角落玩对周围干扰极小。
锻炼手眼协调与节奏感游戏本质上是一个匀速旋转参考系下的时机判断训练。要稳定通关,你必须在脑子里建立一个内部节拍器:看到针刚扫过这一帧就发射,预留出针飞行的那 120ms 延迟。这种训练对动作类游戏的预判、乐器节奏感都有迁移作用,是比单纯反应测试更复杂一层的输入控制。
在课堂或团建里做即兴小竞赛把 URL 投到大屏上,每人轮流打一局,比看谁先全部通关或在哪一关失败。规则极简,老人小孩都能上手,又不像复杂手游需要账号注册,作为团队破冰、小学课堂电脑课的活跃环节非常合适。结果只在浏览器本地保存,不留数据痕迹。
在低配电脑或老旧手机上跑得动整个游戏只用 DOM 元素和 CSS transform 实现,单帧只更新最多 15 个 rotate(deg),连画布都不需要,因此在 4G 内存的旧笔记本、千元安卓机、教室电脑上都能稳定 60fps 运行。也因为没有依赖任何后端,断网状态下也能继续玩。
用作前端动画与节奏控制的演示样本对前端学习者来说,本工具是一个紧凑而完整的「requestAnimationFrame 主循环 + 命中检测 + 状态机」案例:旋转更新、发射、命中判定、连击缓冲、过关判定都在同一个组件里。可以用它来观察 transform 的 GPU 合成行为,对比直接改 left/top 与改 transform 的性能差异,也适合演示 useRef 在高频动画里替代 useState 的取舍。

技术原理

游戏的主循环由 requestAnimationFrame 驱动,每帧把内部 rotation 增加一个固定的 speed 值(单位:度/帧),然后遍历所有已钉入的针元素,把 transform 设为 rotate(baseAngle + rotation)。因为 transform 是合成层属性,浏览器可以把旋转交给 GPU 合成线程处理,不会触发布局或重绘,所以即使最后一关同时有 15 根针在旋转也能稳定 60fps。 命中检测采用「角度差转弦长」的方式:每根针在圆盘坐标系下用它的 baseAngle 表示(相对圆盘的固定位置,不随旋转改变)。新针抵达中心时,它在世界坐标系下的角度是 TARGET_ANGLE = 0,对应到圆盘坐标系下就是 normalizeAngle(0 - rotation)。然后比较新 baseAngle 和所有已有 baseAngle 的角度差 Δθ,转换为针顶端圆点中心间的弦长 2R·sin(Δθ/2)(R = 111px 是顶端圆点中心到圆心的距离)。如果小于 24px 阈值就算撞针,立即结束本局。 发射动作做了简单的状态机控制:shootLocked 标记一根针在飞行中,防止同一根针的 transitionend 和兜底 setTimeout 触发两次结算;针的飞行时间由 CSS transition 控制为 120ms,180ms 的 setTimeout 兜底是为了在极少数情况下 transitionend 没有触发(例如 DOM 在过渡完成前被改动)时仍能完成结算。pendingShots 缓冲玩家在飞行中连续点击的发射请求,上限为 3 发,超过 3 发的点击直接丢弃,这样既能保留快速连射体验,也不会让多根针同时挤向中心。失败和过关都把 gameOver 置为 true,主循环虽然继续运转,但不再更新 rotation,相当于让画面定格。 为了适配不同分辨率,游戏整体保持 420×720 的设计尺寸,外层根据视口宽高计算缩放比例,套在容器上用 transform: scale。当前实现把上限锁在 1,所以小屏会等比缩小、桌面端大屏不会进一步放大;命中检测用的全部都是设计坐标系下的数字,不需要为每个屏幕重新计算阈值,逻辑简单且任何屏幕上判定都一致。

  • 主循环:requestAnimationFrame,每帧 rotation += speed,所有针元素 transform: rotate(baseAngle + rotation)。
  • GPU 加速:旋转使用 transform 而非 left/top,浏览器合成层处理,不触发布局/重绘。
  • 命中检测:把世界坐标的 0° 反推回圆盘坐标 normalizeAngle(0 - rotation),再用弦长公式 2R·sin(Δθ/2) 与 24px 阈值比较。
  • 状态机:shootLocked 防止重复结算,pendingShots 上限 3 发并截断超出(不是排队),gameOver 后冻结 rotation 更新。
  • 结算回调:CSS 飞行 120ms,transitionend 主结算 + 180ms setTimeout 兜底,应对 transitionend 偶发不触发的情况。
  • 适配方案:固定 420×720 设计坐标系,外层 transform: scale 按视口缩放(上限锁定 1,桌面端大屏保持原始尺寸)。
  • 数据保存:仅在 localStorage 中记录历史最佳关卡,不上传任何数据,没有账号体系。

示例

10 关速度与针数配置

关卡   起始针数  本关发射  转速(度/帧)
1      2         8         1.35
2      3         9         1.50
3      3         10        1.65
4      4         11        1.80
5      4         12        2.00
6      5         13        2.20
7      5         14        2.45
8      6         15        2.70
9      7         15        3.00
10     8         15        3.35

典型一局:第 5 关失败

第 5 关:初始 4 根针,需要发射 12 根。
第 1-7 发:依次插入空隙,剩余 5 根。
第 8 发:你在前一根落地前抢点,发射时机太早。
判定:新针 baseAngle 与第 6 发只差 8°,小于 DOT_HIT_DISTANCE。
结果:相撞,本局结束,提示「游戏失败」。
可以点「重玩本关」从第 5 关重新开始。

全部通关结算

通关耗时:约 2 分 40 秒
累计发射:117 根
失败次数:3
历史最佳:第 10 关
提示:「全部通关!」按钮显示「再玩一次」。
历史最佳写入 localStorage,刷新页面仍然保留。

常见问题

为什么我明明点中了空位还是失败了?

因为针不是瞬间到位的,从你点击到针真正接触圆盘大约有 120ms 的飞行时间。这段时间圆盘还在转,如果点击时空隙正好够大,但飞行中圆盘转过去把另一根针带到了正前方,新针落下时就会和它撞上。简单办法是在空隙刚扫过中线时就发射,预留出转动时间。

可以一直按住屏幕连发吗?

可以连续点击,但发射节流是确定的:上一发针没落地前不会发射下一发。在飞行期间最多缓冲 3 次点击依次飞出,超过 3 次的点击会被直接丢弃,不会排队。后期关卡建议放慢点击节奏,等针落地再判断下一发。

游戏会越来越快是按什么规则?

每一关都有独立配置的转速、起始针数和本关需要发射的针数。从第 1 关的 1.35 度/帧到第 10 关的 3.35 度/帧,转速接近 2.5 倍。同时起始针数也从 2 涨到 8,留给你的空隙越来越小,所以越到后面越需要稳,而不只是手快。

失败后是从头来还是从本关来?

两种都支持,但要分清触发方式。点「重玩本关」按钮会从你失败的那一关重开,针数和转速保持不变;点「重新开始」按钮、或在失败画面下按空格 / 回车键,都会回到第 1 关从头打。键盘快捷键不会触发「重玩本关」,习惯按空格继续的玩家要留意。历史最佳关卡始终保留在 localStorage 中。

为什么游戏区域显得比较窄?

游戏的设计尺寸是 420×720,仿照手机竖屏比例。这样在手机上是天然全屏;在桌面浏览器中外层会按视口大小等比缩放,但缩放上限锁定在 1,所以大屏不会进一步放大,看起来就维持在原始 420×720。如果你想要更大的画面,目前只能保持原始比例;游戏不会因为窗口大小改变判定规则。

游戏数据会上传到服务器吗?

不会。整个游戏完全在浏览器本地运行,唯一保存的是「历史最佳关卡」这一个数字,存在 localStorage 里。清除浏览器站点数据或换浏览器就会丢失。我们没有排行榜,也不收集任何分数或操作记录。

手机上玩有没有什么额外注意?

建议在浏览器中开启全屏模式,避免下拉手势误触刷新。横屏其实也能玩,但竖屏比例更接近原版手感。低端机如果出现轻微掉帧,可以试着关闭其他后台标签或减小浏览器窗口里的其他动画元素,本游戏单帧只在做几个 transform 计算,硬件占用极低。