欢迎光临散文网 会员登陆 & 注册

TETRIS—基于现代官方规则俄罗斯方块实现案例(c/c++,EasyX,附完整代码)

2023-02-18 10:06 作者:墨奕白_  | 我要投稿

简介:

作为方块圈的玩家,和刚接触 EasyX 的萌新。我希望大家能够重新认识一下这款经久不衰的游戏。

官方规则是基于俄罗斯方块公司(The Tetris Company,TTC)公司授权的方块游戏规则。如俄罗斯方块效应,噗哟噗哟VS俄罗斯方块等游戏采用的规则。

大部分人对方块的印象大多还停留在上个世纪的  ”消消乐“ 吧?我将一边科普现代方块规则, 一边介绍个人的实现思路,而不是随随便便写一个消消乐。

也希望大家可以去体验一下方块的魅力 !

先来介绍一下现代方块界面吧

噗哟噗哟 VS 俄罗斯方块 2 游戏界面。

介绍 / 规则
标准场地是 10 X 20 个小方块场地,共有 7 个方块,一般也都采用固定 7 种颜色。比如我看到黄色就知道是正方形来了。

来认识一下各个方块,现代方块以 “ T ” 方块为尊(紫色),“ I ” 方块居其次(棍子/长条),红色 Z 绿色 S,一个正方形 O 和 JL。

七个字母对应形状,方块也是有名字的,TIOZSJL 每个方块都由 4 个小方块组成,场地里填满一行就会消除,然后相爱相杀!

HOLD:储存一个方块,可与当前操作的方块交换一下。(是从上面重新下来的不是直接和当前位置交换,每次锁定前只能交换一次)

NEXT:这个好理解看见下一个方块是什么。(官方的好像没有超过 5next 的)

GHOST:影子,可以看见当前方块落下后的样子。(游戏里都是可以选择开启/关闭的)

7bg:七巡,方块的出块规则。随机排列这 7 个不同方块为一包,一包一包出方块。

操作:软降(加速下落)/硬降(当前方块直接下落到最底部锁定)左移动/右移动/软/顺时针旋转/逆时针旋转/ HOLD(暂存按键)。标准七个按键。

方块落到最下面不会锁定,还可以旋转和移动如果 1.5 秒(大约)不操作才会锁定,硬降就是直接落下锁定。

对战伤害系统就先不提了,可以来看看下面的视频链接。(与人斗其乐无穷!)

【国区最强噗哟噗哟俄罗斯方块2比赛(上)】

代码思路解析(个人理解)
场地:想必大家也都理解要用二维数组储存场地数据,xy 坐标和 ij 数组下标方便后面功能判断。

#define Column 10 // 10 列

#define RowNum 22 // 22 行,现代方块场地理论上应该更高

const int Radius = 20; // 方块大小

// 枚举每一个小方块的填充和未填充状态

enum T_STATE {NoBlock, IsBlock};

// 表示方向

enum DIRECTION {Up1, Right2, Down3, left4};

// 表示当前方块种类

enum TKIND {T, I, O, S, Z, L, J};

// 创建单个方块结构体

class A_TETRIS

{

public:

int x, y; // 储存小方块的中心 X Y 坐标

int i, j; // 储存小方块的在地图数组下标 i j

T_STATE T_state; // 每一个小方块的状态,存在和不存在

IMAGE T_im; // 一个小方块的皮肤

}

// 打包的 4 个方块都有一个旋转中心点

struct FOUR_TETRIS

{

A_TETRIS TheFourT[4]; // 打包 4 个为一个

int centerX, centerY; // 储存旋转中心位置

float centeri, centerj; // 储存旋转中心 ij 下标,为什么是 float 呢,特殊方块中心点不规则

DIRECTION Direction = Up1;

TKIND Kind; // 方块种类

}

// 定义地图数据结构体

class TETRIS_MAP

{

public:

A_TETRIS Map_T[RowNum][Column]; // 地图二维数组

}

由每一个小方块的数据构成一个地图数据,然后画出来。3 个枚举也好理解,地图里就用一个是否有方块状态,有就显示图片没有就不显示。

你也可以直接使用简单的填充矩形,后期随变改皮肤。数组构成一个地图,每个方块由 4 个小方块组成。

那么我再定义一个结构体,里面由包含 4 个小方块数据的一维数组组成。

我直接写进去 4 个元素数据,就组成了我要的任意形状方块。

方块一个一个按顺序出来。那么我定义一个类,表示序列。


class SEVEN_BG// 创建七循相关结构体

{

public:

FOUR_TETRIS Four_T; // 用于初始化加载的方块

vector<FOUR_TETRIS> SevenList; // 储存初始化的 7 个方块链表

vector<FOUR_TETRIS> PutSevenT; // 用来存放固定的 7 个方块

vector<FOUR_TETRIS> NextList; // 存放 NEXT 序列

}

序列存放方块数据,我想到的就是链表,#include <vector> //矢量模板也是链表,我用的这个。

这里用了 3 个链表储存单个方块信息,第一个里只有 7 个方块数据,我直接暴力写入 7 个方块数据,存到这链表里

void initialize() // 简单粗暴的直接写入 7bg 初始数据

{ // 加载顺序 T I O S Z L J 超出边界的方块手动计算赋值一下

Four_T.TheFourT[0] = MapDate.Map_T[1][4];

Four_T.TheFourT[1] = MapDate.Map_T[2][3];

Four_T.TheFourT[2] = MapDate.Map_T[2][4];

Four_T.TheFourT[3] = MapDate.Map_T[2][5];

for (int i =0; i<4; i++)

{

Four_T.TheFourT[i].T_state = IsBlock; // 也就第一次要加载这个

loadimage(&Four_T.TheFourT[i].T_im, _T("image / T.png"));

}

Four_T.centerX = Four_T.TheFourT[2].x; // T 的中心点

Four_T.centerY = Four_T.TheFourT[2].y;

Four_T.centeri = Four_T.TheFourT[2].i; // 记录下标

Four_T.centerj = Four_T.TheFourT[2].j;

Four_T.Kind = T;

SevenList.push_back(Four_T); // 将一个 T 方块信息储存到了 7BG 链表

}

手写七·个方块数据到第一个链表里,这里省略。

第二个链表复制一遍第一个链表,然后写一个简单的随机取出链表里的函数

int Rand7BG(int Max)// 生成两个数之间的随机数。就是选择下一个方块

{ // max 不能为 0。思考一下取 0 的余是啥?

    int num =int(rand()%Max);

    return num;

}

7 个方块嘛,循环 7 次不就全取完了,随机取 7 次,取一次 Max 减一。就得到随机方块顺序,然后存到 第三个序列链表里。

取完了再第二链表再复制一遍第一个链表,再随机取出给第三个序列链表,这就是出块规则了。

当前操作的方块就等于第三个链表里的第一个数据,我定义了一个当前方块类,依然引用 4 个单个方块构成的一个方块结构体。

class NOW_TETRIS// 创建当前方块结构体

{

public:

int EraseRow[4]; // 要消除的行的 i 标

int whatRL = 0; // 右旋转为 1,左旋转为-1

FOUR_TETRIS Now_FourT; // 当前方块信息

FOUR_TETRIS Ghost_FourT; // 存放影子信息

FOUR_TETRIS Rotate_FourT; // 用来计算旋转是否成立

FOUR_TETRIS SeekSpin_FourT; // 正常旋转不行就来计算偏移

}

当前方块信息算一个,影子算一个,也就等于链表里的第一个数据。锁定后删除链表第一个数据然后再复制第一个数据,源源不断的生成。

void OnceDown() // 一次下落执行的数据

{ y + = 2 * Radius;

i + = 1; }

单个方块结构体的移动

void OnceUp() // 一个整体方块的一次向上移动

{

for (int i =0; i<4; i++)

TheFourT[i].OnceUp();

centeri - = 1;

centerY - = 2 * Radius;

}

移动简单,下落还是移动都是这种一格一格的,调用然后放到循环里更新就好了。判断能不能移动和下落稍微麻烦一点点而已。


int InspectDown(A_TETRIS &Now, A_TETRIS &Next) // 检查下一个下降位置是否有冲突

{

if (Now.i > = RowNum - 1 || Next.T_state ! = NoBlock)

return 1;

else return 0;

}

int JudgeWhetherDown() // 判断是否可以下落

{

for (int i = 0; i<4; i++)

{ // 下面这串是检查当前方块下路是否和地图里的有冲突

if (InspectDown(Now_FourT.TheFourT[i], MapDate.Map_T[Now_FourT.TheFourT[i].i + 1][Now_FourT.TheFourT[i].j]))

return 0;

}

return 1;

}

每一个小方块里都有一个枚举表示当前是否有方块,也有数组下标数据,提前判断一下它移动后的位置有没有存在方块,和边界判断,如上。

我这里旋转也是提前将旋转过后的数据存一个然后和地图数据比对。

旋转在现代方块规则里是非常重要的!绕旋转中心点旋转,旋转中心各位可以运行后面源文件里程序观看,如下。

旋转中心每个方块都不一样,这个数据也在一开始链表里写数据的直接写了,在 4 个小方块结构体里也定义了中心点数据。以这个点是以为官方标准,旋转系统称为 SRS 系统。

如何通过旋转中心坐标计算旋转后 4 个小方块的坐标。下面这个可是我打了不少草稿发现规律简化的!


A_TETRIS LoadRspin(A_TETRIS &oneT) // 载入右旋转,输入一个单方块用来计算他旋转后的坐标位置

{

float xd = centerX - oneT.x;

float yd = centerY - oneT.y;

float jd = centerj - oneT.j;

float id = centeri - oneT.i;

oneT.x = centerX + yd;

oneT.j = centerj + id;

oneT.y = centerY - xd;

oneT.i = centeri - jd;

return oneT;

}

A_TETRIS LoadLspin(A_TETRIS &oneT) // 载入左旋转,输入一个单方块用来计算他旋转后的坐标位置

{

float xd = centerX - oneT.x;

float yd = centerY - oneT.y;

float jd = centerj - oneT.j;

float id = centeri - oneT.i;

oneT.x = centerX - yd;

oneT.j = centerj - id;

oneT.y = centerY + xd;

oneT.i = centeri + jd;

return oneT;

}

上面就是如何通过旋转中心坐标计算旋转后 4 个小方块的坐标。这个可是我打了不少草稿发现规律简化的!

得到旋转过后的数据了,要和地图里的数据比较。防止重叠嘛,但是当方块移动到边上后,旋转过后的样子肯定超出边界了。

此外还有特殊旋转,你可能会觉得下面这个有点离谱。但这都是真的,当然也是有条件的,也有别的奇怪旋转。

这里就要用到官方设定偏移表了。(千万不要研究什么旋转规律!我痛苦的回忆!)

以下面 i 块为例子,我也会在压缩包里放上完整偏移表。


i 的偏移最特殊,虽然它只有横竖两个状态,注意看旋转中心位置,它是不一样的。

来看看消除


int JudgeErase(int &row) // 判断当前行是否消除

{

int clear = 0; // 判断是否清除。满足 10 行就清除

for (int i = 0; i< Column; i ++)

{

if(Map_T[row][i].T_state == IsBlock)

clear ++;

}

if (clear == 10)

return row;

else return - 1;

}

void eraseline(int &row) // 获取到要消除的行,执行消行后需要做的

{

for (int j = 0; j<Column; j ++) // 首先删除当前行的方块

Map_T[row][j].T_state = NoBlock;

for (int T = row - 1; T>=0; T--) // 当前要消除的上一行开始遍历

for (int j = 0; j<Column; j ++) // 列

{ // 要消除的肯定得是不是方块状态

if (Map_T[T][j].T_state == IsBlock) // 只有遍历到存在方块的数据才开始执行下落

{ // 降消除后落下的方块的信息修改正确

Map_T[T + 1][j].T_im = Map_T[T][j].T_im;

Map_T[T + 1][j].T_state = IsBlock;

Map_T[T][j].T_state = NoBlock; // 当前方块给下面后删除

}

}

}

我这里是每次落下方块,获取当前落下方块的 4 个小方块的 i 坐标,对应地图里那一行有没有满足 10 个,满足就代表这一行要消除,让当前行消失,状态为 noblock 没有方块。上面的方块往下移动一格,从当前 i 行便利走最上面第 22 行,对应 i 下标为 0。

HOLD 交换功能就是和当前方块交换一下嘛,然后显示在右上角就好了。下落就写个时间触发移动一下,不能移动后过 1 秒不操作就锁定,数据写到地图里。

GOHST 影子数据一开始肯定和当前方块一样位置也一样,直接判断影子方块能不能往下移动一格,这个判断放到 while 里,可以下落就继续下落,直到不能下落才是影子位置的数据。

写一个找到影子位置的函数,只在方块左右移动的时候调用一下,别的操作不要调用。移动还有 das 什么操作要求啥的,等我下次把别的功能做好再来更新吧。目前我写的这个操作起来还不错,我就是玩方块的玩起来手感还行哦。

好就写到这吧,源代码都有注释应该容易理解,比较详细。(有些注释自己写着玩的请见谅)

http://farter.cn/t/ 块圈群主“屁大爷”的屁块!(挖掘模式很有意思很上头)

http://tetr.io   目前圈里很火的在线对战方块

墨白的源码 :https://pan.baidu.com/s/1FmIBp5sobAqZb-h870BWWw?pwd=gi68 

提取码:gi68 


调试环境 VS200 (没有EasyX库的可以去官网看一下https://easyx.cn/)








TETRIS—基于现代官方规则俄罗斯方块实现案例(c/c++,EasyX,附完整代码)的评论 (共 条)

分享到微博请遵守国家法律