夜雨聆风学习资料网

ARTICLE · 1280724

在小程序里玩上了用 AI 做的2048小游戏

在小程序里玩上了用 AI 做的2048小游戏

Hi,我是松柏。

我有一个自己做的小程序,里面放的都是一些实用小工具:

不过工具箱里全是干巴的东西,总觉得少了点乐趣。

正好最近刷 X 的时候看到了一个 2048 小游戏,于是就想着干脆让 AI 给我的小程序也加上这么个小游戏。

最后做出来的效果是这样的:

还有一个排行版功能:

接下来就给大家介绍下这个小游戏的玩法,以及 AI 是怎么把它实现出来的,希望大家能有所收获和启发。

点赞关注,我们现在发车~

❝

文末有小程序直达链接,点击即玩~

玩法介绍

基本规则

2048 的规则相信大家都很熟悉了:

  • 在 4×4 的棋盘上上下左右滑动,所有方块会一起往那个方向移动
  • 两个数字相同的方块撞到一起就会合成一个,比如 2 + 2 = 4,4 + 4 = 8
  • 每滑一步,空位上会随机冒出一个 2 或 4
  • 合成出 2048 就算赢,棋盘满了又没法合并就结束

每次合并得到的数字就是本次得分,比如合成一个 16 就加 16 分。

分数卡

棋盘上方是三张分数卡:

  • 当前分数:本局得分,下面会提示“差 X 分破纪录”,超过之后变成金色的“新纪录!”
  • 今日最高:今天的最好成绩,附带今日榜名次
  • 历史最高:有史以来的最好成绩,附带总榜名次

分数在本地存一份,同时会和服务端的记录取较大值。

所以就算换了手机或者清了缓存,历史最高分也能找回来。

撤销和存档

这几个是 AI 调研同类小程序之后加上的,我自己玩下来觉得都挺实用:

  • 撤销:每局可以撤 3 次,游戏结束的时候也能撤一步“续命”
  • 自动存档:玩到一半退出,下次进来接着玩
  • 继续挑战:合成 2048 之后可以选择继续,冲击 4096、8192
  • 统计:本局最大方块、步数、累计局数

排行榜

排行榜分总榜和今日榜,默认展示总榜,各显示前 50 名:

  • 同分并列名次
  • 前三名有金银铜牌
  • 自己那一行会高亮,顶部单独显示我的名次和上榜人数

榜上的昵称用的是小程序“我的”页里设置的昵称,注意这里会过一遍微信的内容安全检查,不合规的话就显示成“2048 玩家”。

分享挑战

分享是我最喜欢的一个功能。

点分享的时候,标题会带上你的分数,分享卡片也不是一张固定的图,而是现场画出来的:左边是你当前的棋盘,右边是分数。

好友从这个卡片点进来,页面上会显示“好友邀请你挑战 X 分”,超过之后提示挑战成功。

音效和震动

每次合并都有一个轻轻的“啵”声,而且音调会跟着数字变:合成 4、8、16……音调一级一级往上走,越往后合越有成就感。

震动只在关键时刻出现,比如合出 256 以上的大方块、破纪录、合成 2048、游戏结束。

底部有音效和震动两个开关,嫌吵的话可以关掉。

实现过程

整个小游戏从调研、写代码到测试,基本都是 Claude 完成的,我主要负责提需求和在开发者工具里看效果。

先看下整体结构:

模块
作用
游戏引擎
棋盘状态、移动合并、撤销、胜负判定,纯逻辑不碰界面
游戏页面
手势识别、滑动动画、分数卡、音效震动、分享卡片
排行榜页面
总榜 / 今日榜切换、我的名次
后端接口
上报成绩、查询个人汇总、查询排行榜

前端放在一个新的分包里,主包几乎不受影响。

移动和合并

2048 最核心的就是移动合并这一步。

不管往哪个方向滑,都先把每一行(或每一列)按“靠近移动方向的一端”排好序,然后统一按“往左滑”的逻辑处理:

while (i < line.length) {
const current = line[i]
const next = line[i + 1]
const cell = cells[target]
if (next && next.value === current.value) {
// 相邻两个相同:合成一个新方块,两个旧方块都滑到目标格后消失
const value = current.value * 2
    settled.push({ id: nextId, value, row: cell.row, col: cell.col, merged: true })
    gained += value
    i += 2
  } else {
// 不同:直接滑到目标格
    settled.push({ ...current, row: cell.row, col: cell.col })
    i += 1
  }
  target += 1
}

这里有三个关键点:

1)合并完 i += 2,跳过已经合并的方块,保证 2 2 2 2 往左滑是 4 4,而不是一次合成 8

2)如果一步下来没有任何方块移动,就不算有效操作,也不会生成新方块

3)每步之前会存一份快照,撤销的时候直接恢复,最多存 3 份

滑动动画

方块滑动的效果是用“两帧”来做的:

  • 第一帧:所有旧方块(包括即将被合并掉的)用 CSS transform 滑到新位置,过渡时间 110ms
  • 第二帧:滑动结束后,换成合并后的结果,新合成的方块弹一下,新生成的方块淡入

每个方块都有一个固定的 id,小程序列表渲染时用它当 wx:key,这样同一个方块只是改了位置,过渡动画才能生效。

手势这块比较简单:记录手指按下和抬起的坐标,移动距离超过 24px 才算一次滑动,横向和纵向哪个距离大就往哪个方向走。

音效

音效我原本以为要找一堆音频文件,结果 AI 直接用微信的 WebAudio 接口现场合成,一个音频文件都没有,包体积不变,延迟也低。

合并音的音调是按五声音阶排的:

const PENTATONIC = [0, 2, 4, 7, 9, 12, 14, 16, 19, 21, 24, 26, 28, 31, 33]

functionnoteFreq(step: number) {
const index = Math.max(0, Math.min(PENTATONIC.length - 1, step))
return523.25 * Math.pow(2, PENTATONIC[index] / 12)
}

简单解释一下:523.25 是 C5 这个音的频率,合出的数字越大,step 越大,音就越高。用五声音阶是因为它没有半音,连着听也不会刺耳。

在此基础上,一步合并两组会叠一个音,合出 128 以上再叠一个更亮的高音。撤销、破纪录、合成 2048、游戏结束也各有一段小旋律。

成绩和排行榜

后端新增了三个接口:上报成绩、查询我的汇总、查询排行榜,启动时会自动建两张表。

成绩是按“局”存的。同一局中途退出、游戏结束都会上报,入库的时候用 GREATEST 只保留这一局的最高分,所以重复上报也不会出问题。

今日榜按北京时间算“今天”,名次就是“分数比我高的人数 + 1”,所以同分会自然并列。

另外,AI 还给上报加了一道粗校验,挡掉直接伪造请求的离谱分数:

// 每步最多新生成一个 4,棋盘总和 ≤ 4 × (步数 + 2)
const sumBound = 4 * (moves + 2)
if (maxTile > sumBound) returnnull
// 同一份数值最多被合并 log2(最大方块) 次
if (score > sumBound * Math.log2(maxTile)) returnnull

比如只走了 10 步就说自己合出了 2048,这种请求就直接拒掉。当然这只能防住瞎编的请求,真要改客户端作弊是防不住的,自娱自乐的小游戏够用了。

分享卡片

分享卡片是在页面里藏了一个 canvas,分享的时候把当前棋盘和分数画上去,再导出成临时图片交给 onShareAppMessage。

分享路径里带上了分数参数 ?challenge=分数,好友打开时页面读到这个参数,就会显示挑战提示。

结语

这篇文章就到这里啦,如果有帮助或者启发的话,麻烦点个关注和赞吧,非常感谢~

另外,这个小游戏已经上线了,点击下面的卡片就能直接玩,欢迎来排行榜上挑战我的分数~

下期再见,拜拜👋🏻。

回首页,搜索你想要的文档

在首页搜索框输入关键词,快速定位资料

文库首页搜索指引 立即搜索

相关学习资料