纯前端实现一个丝滑的 2048:核心算法与交互细节拆解

举报
yd_263393080 发表于 2026/10/24 00:02:25 2026/10/24
【摘要】 不依赖任何框架和构建工具,用单个 HTML 文件实现完整 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 文件,欢迎自行取用改造,比如加上"自动求解"或者换一套配色。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。