单文件2048实战:合并规则、触摸操作与进度保存

举报
yd_279019822 发表于 2026/10/22 12:54:32 2026/10/22
【摘要】 基于实际编写和浏览器验证的单文件2048,梳理棋盘状态、四方向合并、结束判定、手机手势、一步撤销与localStorage恢复,并说明本地验证与云端部署验证的边界。

一个 2048 小游戏可以成为检验前端基本功的完整练习:状态如何表示、合并是否正确、手机手势如何处理、刷新能否恢复,以及怎样证明实现符合预期。本文基于本次实际编写并在浏览器测试的“竹影 2048”,整理单文件 HTML 的实现思路。页面的 CSS 和 JavaScript 全部内联,不依赖 npm、打包工具或外部图片。开发过程使用了 AI 辅助,文中的验证结果来自实际运行检查。

一、先定义棋盘状态,再编写界面

棋盘使用长度为 16 的一维数组表示,0 代表空格。第 r 行、第 c 列对应的下标为 r * 4 + c。分数、最高分、是否已经选择在达到 2048 后继续游戏,以及上一回合的快照,分别保存在独立变量中。这样可以把“如何计算一次移动”与“如何展示棋盘”分开。

一次移动的顺序是:计算新棋盘,判断是否有变化,保存撤销快照,更新棋盘和分数,生成一个新方块,最后渲染。没有变化的方向不能新增方块;否则用户连续按住一个无法移动的方向,也会把棋盘填满。

二、合并的关键:每个方块一回合只参与一次

以向左移动为例,先去掉一行中的 0,再从左到右扫描相邻值。相等时合并并跳过下一项,不等时直接保留,最后补 0。下面的纯函数只接收一行并返回结果,不读写页面或存储,方便验证边界情况。

function mergeLine(line) {
  const values = line.filter(Boolean);
  const out = [];
  let gained = 0;
  for (let i = 0; i < values.length; i++) {
    if (values[i] === values[i + 1]) {
      out.push(values[i] * 2);
      gained += values[i] * 2;
      i++;
    } else {
      out.push(values[i]);
    }
  }
  return {
    line: out.concat(Array(4 - out.length).fill(0)),
    gained
  };
}

最容易写错的输入是 [2, 2, 4, 0]。向左移动后应为 [4, 4, 0, 0],得分增加 4,不能在同一回合继续合成 8。另一个检查是 [2, 2, 2, 2]:结果应为 [4, 4, 0, 0],增加 8 分。

三、让四个方向共用同一个合并算法

向右、向上、向下不必各写一份合并代码。可以先建立每行或每列的四个数组下标。向右和向下时反转下标顺序,按这个顺序提取数据并交给 mergeLine,再将结果写回原下标。这样四个方向只有取数顺序不同,合并规则始终一致。

移动前后逐格比较即可判断 changed。changed 为 false 时直接结束本次操作,不更新分数、不生成方块,也不覆盖撤销快照。生成新方块时先收集所有空位,再随机选择其中一个;本实现以 90% 概率生成 2、10% 概率生成 4。

四、满格不等于游戏结束

游戏结束需要同时满足两个条件:没有空格,且没有水平方向或垂直方向的相邻同值方块。扫描每个格子时,只检查右侧和下侧邻居就足够,避免重复检查。存在任意空位或可合并邻居,就仍可继续。

达到 2048 与无路可走是两个不同状态。前者显示继续游戏按钮;后者显示本局得分和重新开始按钮。继续游戏时只更新 continued 标记,不重置棋盘。

五、手机滑动和键盘输入共用入口

键盘监听方向键与 WASD,将它们映射为 left、right、up、down,再统一调用 move。处理有效方向键时阻止页面滚动,但当焦点位于输入框、文本域或可编辑区域时,不应拦截正常输入。

手机端记录 pointerdown 与 pointerup 的坐标差。当位移小于 24 像素时按点击处理,不移动棋盘;超过阈值后,比较水平和垂直位移的绝对值,较大的轴决定方向。棋盘设置 touch-action: none,只限制棋盘内的默认手势,页面其他区域仍可滚动。pointercancel 发生时清空起点,避免下一次滑动沿用旧坐标。

六、进度保存与撤销需要不同的边界

一次有效移动前,复制当前棋盘、分数和 continued 标记作为上一回合快照。撤销时恢复该快照,并清空它,提供明确的一步撤销能力。最高纪录作为历史统计,不随撤销降低。

棋盘与得分通过 localStorage 保存,读取时放在 try/catch 中。浏览器可能禁用存储,存储内容也可能损坏,游戏应退回到新棋盘而不是白屏。恢复前还要验证:数组长度为 16、元素为 0 或不小于 2 的 2 的幂、分数为非负整数。localStorage 只代表当前浏览器当前来源下的进度,并不提供跨设备同步。

七、用具体检查证明行为,而不是只看页面

本次实际检查了:连续四个同值方块的单次合并、合并后的方块不能二次合并、向右压缩空位、向上与向下的列合并、满格棋盘的结束判定、仍有相邻同值时继续游戏,以及无变化移动不新增方块。

浏览器操作检查还覆盖了键盘移动、撤销恢复原棋盘与分数、刷新后进度恢复,以及 390 像素宽度下页面没有横向溢出。手机布局检查与合成指针事件只能覆盖部分行为,真实设备上的触摸体验仍适合补充测试。

这些检查通过后,可以把 index.html 放到静态网页服务上验证。云端是否成功部署,需要另外确认公网地址能返回正确游戏内容;仅有环境 ID 或仅看到 HTTP 200,都不能代替完整验证。本次游戏的本地实现和浏览器检查已完成,华为云沙箱的公网部署尚未成功,因此本文不将云端部署描述为已完成。

八、这个练习可以继续如何扩展

下一步可以补充移动动画、颜色对比度检查、真实设备触摸测试,以及可重复的随机种子。继续保持计算函数独立,让界面特效不改变游戏规则,通常比一次加入更多功能更容易维护。单文件的价值在于便于阅读和交付;当代码规模增加时,再按状态计算、输入处理和渲染职责拆分即可。

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

评论(0)

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

全部回复

上滑加载中

设置昵称

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

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

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