纯前端实现一个丝滑的 2048:核心算法与交互细节拆解
2048 大概是被重复实现次数最多的小游戏了。但它其实是个非常适合练基本功的项目:状态管理、算法抽象、移动端适配、动画节奏,一样不少。最近用纯原生技术栈(无框架、无构建工具、单个 HTML 文件)完整实现了一遍,把值得记录的东西整理成这篇笔记。
一、为什么做单文件版
零依赖意味着双击即玩,随便一个静态服务器(甚至 python3 -m http.server)就能托管,不存在 npm install 十分钟的问题。整个游戏最终只有一个 index.html:CSS 写在 style 标签里,JS 写在 script 标签里,加起来不到 300 行。更重要的是,脱离框架之后,每一个细节都必须自己想清楚,反而对理解问题本身更有帮助。
二、核心:四方向合并的统一抽象
这是整个游戏最值得设计的一处。新手最常见的写法是给上下左右各写一套遍历逻辑,四份代码高度相似又各有细节差异,改一处漏三处。更好的做法是把四个方向归一到同一个抽象上:
左滑/右滑 → 取出一行;上滑/下滑 → 取出一列
右滑/下滑时先把序列反转,统一成"向左合并"
合并完成后反转写回
这样四个方向共享同一份核心函数 getLine / slideLine / setLine,每个方向只决定"取序列"和"是否反转"两件事:
function getLine(direction, index) {
if (direction === 'left') return grid[index].slice();
if (direction === 'right') return grid[index].slice().reverse();
if (direction === 'up') return grid.map(row => row[index]);
return grid.map(row => row[index]).reverse();
}
slideLine 是纯函数:输入一行的数值数组,输出去零压缩、相邻等值两两合并、尾部补零后的新数组,顺便把本次得分带出来。纯函数的好处是天然可测试,撤销功能也顺手就能做。
三、生成与判定
新块的生成规则是经典设定:空格中随机挑一个,90% 概率出 2,10% 出 4。写成 Math.random() < 0.9 ? 2 : 4 就够了。
胜负判定反而容易写错。游戏结束的充要条件是:没有空格,且任意相邻两格不相等。遍历时只需要检查每个格子右边和下边的邻居,不用完整四方向模拟一遍移动,O(n²) 一次扫描即可:
function movesAvailable() {
for (let r = 0; r < 4; r++)
for (let c = 0; c < 4; c++) {
const v = grid[r][c];
if (v === 0) return true;
if (c + 1 < 4 && grid[r][c + 1] === v) return true;
if (r + 1 < 4 && grid[r + 1][c] === v) return true;
}
return false;
}
四、撤销栈
撤销是体验加分项,实现上就是一个快照栈:每次"有效"移动前把当前网格深拷贝压栈;用户点撤销时弹栈恢复。两个细节:一是无效移动(没有任何方块变化)不压栈,否则撤销会出现"按了没反应"的感觉;二是深拷贝用 grid.map(row => row.slice()),两层都要拷,直接 slice() 只拷外层是经典坑。
五、交互细节
这部分的琐碎程度超出预期,逐条记录:
1. 触摸手势的误触过滤。touchend 时计算位移,位移的最大分量小于 28px 就忽略——不然点一下也会触发滑动。方向判定取 dx/dy 绝对值较大者,避免斜向滑动时的抖动歧义。
2. touch-action: none。棋盘元素必须设置,否则在手机上滑动手势会和页面滚动打架,出现"方块动了一半页面也滚了"的诡异表现。
3. 字号自适应。128、1024 这些四位数在固定字号下会溢出格子。用 clamp() 让字号随棋盘宽度缩放(tile-1024 用更小的一档),从 375px 宽的手机到桌面端都能正常显示。
4. 动画节奏。新块出现用 0.14s 的 scale 弹入,快而不闪。试过 0.3s 的版本,连续操作时明显觉得"拖泥带水"——动画时长是体感问题,参数只能调出来。
5. 键盘防滚动。方向键监听里 preventDefault(),否则按上下键时页面跟着滚。
六、小结
整个实现下来最大的感受:小游戏麻雀虽小,抽象做得好,代码量能砍掉一大半(四方向归一这一处就省了约 80 行重复代码);而体验差距往往不在算法,在那些 28px、0.14s、touch-action 之类的细节参数里。完整代码就一个 HTML 文件,欢迎自行取用改造,比如加上"自动求解"或者换一套配色。
- 点赞
- 收藏
- 关注作者
评论(0)