2048移动算法的三条不变量:一次合并、有效移动与结束判定
做一个2048小游戏,最容易出错的部分是数字合并。界面可以很快搭起来,但只要合并顺序、随机生成或结束条件处理不当,同一个局面就可能得到意外结果。下面以我发布的《2048·数字漫游》为例,整理三条适合检查实现是否正确的不变量。
一、新合成的数字在本次移动中不再参与合并
向左移动时,先去掉空格,再从左到右扫描。遇到两个相同数字就输出它们的和,同时跳过已被消耗的下一个数字。扫描结束后,在末尾补零,使一行仍有四个位置。
例如[2,2,2,2]应得到[4,4,0,0],增加8分;[2,2,4,0]应得到[4,4,0,0],增加4分。后一种情况中,新生成的4不能立刻与原来的4继续合并成8。通过输出到一个新数组,可以自然避免把刚合成的数字再扫描一次。
function mergeLine(line) {
const source = line.filter(Boolean);
const output = [];
let gained = 0;
for (let i = 0; i < source.length; i++) {
if (source[i] === source[i + 1]) {
const value = source[i] * 2;
output.push(value);
gained += value;
i++;
} else {
output.push(source[i]);
}
}
while (output.length < 4) output.push(0);
return { line: output, gained };
}
这个函数只接收一行、返回新结果,不修改传入数组,也不操作页面。测试时可以直接比较输入和输出,无需启动整局游戏。
二、无效移动不能改变局面
处理一个方向时,先从棋盘提取四行或四列,统一调用mergeLine,再将结果放回对应位置。向右或向下移动时反转取数顺序,合并规则仍然只保留一份。
得到新棋盘后,应比较它与原棋盘是否存在不同位置。假如棋盘已经紧贴左侧,再按一次左键且没有可合并数字,这次操作没有产生有效移动。此时不应生成新的随机数字,也不应覆盖上一次有效移动的撤销记录。
有效移动的状态更新顺序可以固定为:保存旧棋盘与旧分数、应用合并结果、累加分数、在空位生成一个2或4、最后刷新界面。先把纯计算与界面更新分开,再把这几个步骤集中到一个入口,键盘、滑动和方向按钮就能共享同一套行为。
三、满盘不等于游戏结束
棋盘中还有空格时可以继续操作;没有空格时,还要检查横向或纵向是否存在相邻且相同的数字。只要有一对可合并数字,游戏就没有结束。
用长度为16的数组表示棋盘时,横向检查要限制在同一行内,不能把一行末尾与下一行开头误认为相邻。可以只向右和向下比较:下标i满足i%4<3时检查i+1,满足i<12时检查i+4。这样每条相邻关系只需要检查一次。
用小范围穷举检查基础性质
我对作品中的实际mergeLine函数做了256组检查:每个位置从0、2、4、8中选择,共4的4次方组输入。检查输出长度仍为4、合并前后数字总和相等,以及输入数组没有被修改;另行核对了上述两个具体合并示例和一个无空格、无相邻相同数字的结束局面。
这些检查覆盖的是基础性质和关键例子,不能代替所有方向、撤销、触控和整局状态的验证。将规则拆成独立计算函数的价值在于:当界面或输入方式变化时,仍可以保留同一组检查,较早发现对游戏规则的意外修改。
做完规则验证后,再检查手机小屏幕布局、键盘操作和按钮可访问性,小项目的实现与使用体验才比较完整。
- 点赞
- 收藏
- 关注作者
评论(0)