直接上试玩地址(手机/电脑均可):https://sct.ctm49.com/tetris

OnOTetris 截图

前言

偶然在Bilibili刷到BV13f4y1r7zn,22行C代码实现的Tetris(俄罗斯方块)视频。想起了以前也想实现一些小游戏,于是,开始动手。

Tetris的基本规则和实现

翻阅了许多Tetris的实现,最终找到了一个比较官方的规则: https://tetris.fandom.com/wiki/Tetris_Guideline
详细规则不复读了,重点在于,每个方块的定义和旋转如下图所示:
tetris_srs.png

方块表示

对于每一个方块,可以用一个16位2进制数来表示,这样每个方块就表示成了一个数,整个方块表现为一个数的数组,使用起来会比较方便。
表示方法举例:
图中第1列第3个方块(橙色),用二进制表示如下:
0010
1110
0000
0000
从最后一列最后一行开始,一列一列倒序记为0000 0011 0010 0010,10进制数为802
以上28个方块于是就可以记为this.tetris = [8738, 547, 802, 51, 306, 562, 561, 3840, 368, 1136, 51, 1584, 624, 864, 17476, 1570, 550, 51, 612, 610, 1122, 240, 116, 113, 51, 99, 114, 54]
如果需要读取第i列第j行是否有方块,只需判断a >> (i * 4 + j) & 1是否为真,即判断二进制表示的第4i+j位是否为1。

在实际实现中,方块我是使用一个对象来表示,赋予它坐标和颜色等性质。

this.T = {
    p: [0,0], // 位置数组p[0]为横坐标(列),p[1]为纵坐标(行)
    color: 1, // 方块颜色下标,使用this.color[1]来获得具体颜色代码
    type: 802 // 方块形状,参考上面
}

移动、旋转以及碰撞

游戏区域是用一个10(列)x20(行)的数组来表示的,每个值用来储存颜色下标,这样可以形成五颜六色的游戏区域。如果只用1 0来表示游戏区域,那么就只有黑白了。
方块的移动,只需将this.T.p加减相应的坐标即可,而方块的旋转则要改变方块的形状,28个方块我是按照图片从上往下再从左往右记录的,因此只需将对应this.tetris下标+7,模28即可得到旋转后的形状。
在移动和旋转方块前,要对方块移动后的位置进行碰撞检测,如果与游戏区域已存在的方块碰撞了则不能移动。

游戏核心逻辑

游戏核心的逻辑就是方块自动下落,如果不能下移一格(发生碰撞),则将方块进行固定,即写入游戏区域数组。固定之后判断是否消行,或是游戏结束(方块越界了),之后再随机产生下一个方块。

并行地,当玩家按W A S D 空格等操作时,对方块进行相应的移动、旋转或者下坠。

具体实现代码就不赘述了,按照上面的逻辑就行。

Tetris AI

看了 https://github.com/LeeYiyuan/tetrisai 项目之后,我决定将这个AI引入我的小游戏(因为我菜)

基本原理

基本原理在https://codemyroad.wordpress.com/2013/04/14/tetris-ai-the-near-perfect-player/ 有介绍,这里总结一下:

要判断方块最佳的位置,那么就要对方块放置后的局面进行打分,选出最佳的局面。在这个AI算法中,定义了总高度(AH, 每列高度和)、消行数(CL, 有多少整行)、孔洞数(H, 有多少上方有方块的空位)和崎岖度(B, 相邻两列高度差绝对值之和),则打分score = a * A H + b * CL + c * H + d * B,其中abcd是参数,通过遗传算法确定:

var [a, b, c, d] = [-0.510066, 0.760666, -0.35663, -0.184483]

那么对一个方块,遍历它所有可能下落的位置,对下落后的局面按照上面的评判标准进行打分,选取最高分的位置下落即可。

提示实现

按照上面的原理,我实现了“提示”的效果,点击左边的提示按钮,即可获得闪烁的橙红色区域,就是AI推荐的方块放置位置。为了防止玩家依赖提示,我设置为点击提示后持续10s的提示效果。

结语

兴致来了实现了我之前构想的SCT项目(在线小游戏项目)的第一个小游戏:Tetris,并且完成了AI的实现,GUI的部分主要涉及到javascript与canvas的互动,这里不想说细节了。下次对什么小游戏的AI有兴趣了,再实现一个。